discord-self-lite 0.1.16 → 0.1.18

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,63 +1,64 @@
1
- {
2
- "name": "discord-self-lite",
3
- "version": "0.1.16",
4
- "description": "Lightweight Discord selfbot library",
5
- "keywords": [
6
- "discord",
7
- "selfbot",
8
- "bot",
9
- "api",
10
- "websocket",
11
- "discord-api",
12
- "discord-bot",
13
- "discord-selfbot",
14
- "lightweight",
15
- "nodejs"
16
- ],
17
- "main": "src/index.js",
18
- "files": [
19
- "src",
20
- "README.md",
21
- "LICENSE"
22
- ],
23
- "exports": {
24
- ".": "./src/index.js",
25
- "./classes/Client": "./src/classes/Client.js",
26
- "./classes/WebhookClient": "./src/connection/webhook/WebhookClient.js"
27
- },
28
- "engines": {
29
- "node": ">=18.13.0"
30
- },
31
- "scripts": {
32
- "test": "eslint . && prettier --check .",
33
- "format": "prettier --write .",
34
- "fix": "eslint . --fix && prettier --write .",
35
- "docs": "echo \"📚 Documentation available at docs/README.md\""
36
- },
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/kyan0045/discord-self-lite.git"
40
- },
41
- "author": {
42
- "name": "Kyan Bosman",
43
- "email": "contact@kyanbosman.com",
44
- "url": "https://github.com/kyan0045"
45
- },
46
- "license": "GPL-3.0-or-later",
47
- "bugs": {
48
- "url": "https://github.com/kyan0045/discord-self-lite/issues"
49
- },
50
- "homepage": "https://github.com/kyan0045/discord-self-lite#readme",
51
- "funding": {
52
- "type": "github",
53
- "url": "https://github.com/sponsors/kyan0045"
54
- },
55
- "dependencies": {
56
- "ws": "^8.18.3"
57
- },
58
- "devDependencies": {
59
- "eslint-config-prettier": "^10.1.8",
60
- "eslint-plugin-prettier": "^5.5.4",
61
- "prettier": "^3.6.2"
62
- }
63
- }
1
+ {
2
+ "name": "discord-self-lite",
3
+ "version": "0.1.18",
4
+ "description": "Lightweight Discord selfbot library",
5
+ "keywords": [
6
+ "discord",
7
+ "selfbot",
8
+ "bot",
9
+ "api",
10
+ "websocket",
11
+ "discord-api",
12
+ "discord-bot",
13
+ "discord-selfbot",
14
+ "lightweight",
15
+ "nodejs"
16
+ ],
17
+ "main": "src/index.js",
18
+ "files": [
19
+ "src",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "exports": {
24
+ ".": "./src/index.js",
25
+ "./classes/Client": "./src/classes/Client.js",
26
+ "./classes/WebhookClient": "./src/connection/webhook/WebhookClient.js"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.13.0"
30
+ },
31
+ "scripts": {
32
+ "test": "node --test tests/*.test.js",
33
+ "lint": "eslint src tests eslint.config.js && prettier --check src tests eslint.config.js package.json package-lock.json .github/workflows",
34
+ "format": "prettier --write .",
35
+ "fix": "eslint . --fix && prettier --write .",
36
+ "docs": "echo \"📚 Documentation available at docs/README.md\""
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/kyan0045/discord-self-lite.git"
41
+ },
42
+ "author": {
43
+ "name": "Kyan Bosman",
44
+ "email": "contact@kyanbosman.com",
45
+ "url": "https://github.com/kyan0045"
46
+ },
47
+ "license": "GPL-3.0-or-later",
48
+ "bugs": {
49
+ "url": "https://github.com/kyan0045/discord-self-lite/issues"
50
+ },
51
+ "homepage": "https://github.com/kyan0045/discord-self-lite#readme",
52
+ "funding": {
53
+ "type": "github",
54
+ "url": "https://github.com/sponsors/kyan0045"
55
+ },
56
+ "dependencies": {
57
+ "ws": "^8.21.1"
58
+ },
59
+ "devDependencies": {
60
+ "eslint-config-prettier": "^10.1.8",
61
+ "eslint-plugin-prettier": "^5.5.4",
62
+ "prettier": "^3.6.2"
63
+ }
64
+ }
@@ -93,6 +93,15 @@ class Message {
93
93
  return this;
94
94
  }
95
95
 
96
+ /**
97
+ * Delete this message
98
+ * @returns {Promise<Message>} This message instance after deletion
99
+ */
100
+ async delete() {
101
+ await this.client.rest.deleteMessage(this.channelId, this.id);
102
+ return this;
103
+ }
104
+
96
105
  /**
97
106
  * Get the guild this message was sent in
98
107
  * @returns {Guild|null} The guild instance, or null if message was sent in DM
@@ -23,14 +23,9 @@ class RestManager {
23
23
  // Rate limiting state
24
24
  this.rateLimits = new Map(); // endpoint -> { remaining, reset, retryAfter }
25
25
  this.globalRateLimit = null; // { reset }
26
- this.requestQueue = [];
27
- this.processingQueue = false;
28
-
29
- // Circuit breaker state for heavily rate-limited routes
30
- this.circuitBreakers = new Map(); // routeKey -> { failures, lastFailure, openUntil }
31
-
32
- // Suspended routes - temporarily disabled endpoints
33
- this.suspendedRoutes = new Map(); // routeKey -> { suspendedUntil, reason }
26
+ this.globalDelay = null;
27
+ this.requestQueues = new Map(); // routeKey -> queued requests
28
+ this.processingRoutes = new Set();
34
29
  }
35
30
 
36
31
  /**
@@ -41,65 +36,24 @@ class RestManager {
41
36
  * @throws {Error} If the request fails
42
37
  */
43
38
  async request(endpoint, options = {}) {
44
- // Check rate limits before queuing the request with more aggressive logic
45
- const method = options.method || "GET";
46
- const routeKey = this.getRouteKey(endpoint, method);
47
-
48
- // Check circuit breaker - reject immediately if route is temporarily disabled
49
- const circuitBreaker = this.circuitBreakers.get(routeKey);
50
- if (circuitBreaker && circuitBreaker.openUntil > Date.now()) {
51
- const remainingTime = circuitBreaker.openUntil - Date.now();
52
- console.log(
53
- `🚫 Request dropped: Circuit breaker open for ${routeKey} (${Math.round(remainingTime / 1000)}s remaining)`,
54
- );
55
- return Promise.resolve(null);
56
- }
57
-
58
- // Check if route is suspended - silently reject to avoid spam
59
- const suspendedRoute = this.suspendedRoutes.get(routeKey);
60
- if (suspendedRoute && suspendedRoute.suspendedUntil > Date.now()) {
61
- const remainingTime = suspendedRoute.suspendedUntil - Date.now();
62
- console.log(
63
- `🚫 Request dropped: Route ${routeKey} is suspended for ${Math.round(remainingTime / 1000)}s`,
64
- );
65
- return Promise.resolve(null);
66
- }
67
-
68
- // Check rate limits - if rate limited, suspend immediately instead of waiting
69
- if (this.isRateLimited(endpoint, method)) {
70
- const suspensionTime = 2 * 60 * 1000; // 2 minutes
71
- this.suspendedRoutes.set(routeKey, {
72
- suspendedUntil: Date.now() + suspensionTime,
73
- reason: "Rate limited - pre-emptive suspension",
74
- });
75
-
76
- console.log(
77
- `⏸️ Suspending route ${routeKey} for 2 minutes (pre-emptive)`,
78
- );
79
-
80
- // Clear queued requests for this route
81
- const clearedCount = this.clearQueuedRequestsForRoute(routeKey);
82
- if (clearedCount > 0) {
83
- console.log(
84
- `🗑️ Cleared ${clearedCount} queued requests for suspended route ${routeKey}`,
85
- );
86
- }
87
-
88
- console.log(`🚫 Request dropped: Route suspended for ${routeKey}`);
89
- return Promise.resolve(null);
39
+ const routeKey = this.getRouteKey(endpoint, options.method || "GET");
40
+ let queue = this.requestQueues.get(routeKey);
41
+ if (!queue) {
42
+ queue = [];
43
+ this.requestQueues.set(routeKey, queue);
90
44
  }
91
45
 
92
46
  return new Promise((resolve, reject) => {
93
47
  // Prevent queue from growing too large during rate limit storms
94
- if (this.requestQueue.length >= 50) {
48
+ if (queue.length >= 50) {
95
49
  console.log(
96
- `🚫 Request dropped: Queue full (${this.requestQueue.length} requests)`,
50
+ `🚫 Request dropped: Queue full for ${routeKey} (${queue.length} requests)`,
97
51
  );
98
52
  resolve(null);
99
53
  return;
100
54
  }
101
55
 
102
- this.requestQueue.push({
56
+ queue.push({
103
57
  endpoint,
104
58
  options,
105
59
  resolve,
@@ -107,7 +61,7 @@ class RestManager {
107
61
  timestamp: Date.now(),
108
62
  });
109
63
 
110
- this.processQueue();
64
+ this.processQueue(routeKey);
111
65
  });
112
66
  }
113
67
 
@@ -115,109 +69,68 @@ class RestManager {
115
69
  * Process the request queue with rate limiting
116
70
  * @private
117
71
  */
118
- async processQueue() {
119
- if (this.processingQueue || this.requestQueue.length === 0) {
120
- return;
121
- }
122
-
123
- // Check if we're globally rate limited before starting
124
- if (this.globalRateLimit && Date.now() < this.globalRateLimit.reset) {
125
- const delay = this.globalRateLimit.reset - Date.now();
126
- console.log(
127
- `⏳ Global rate limit active, waiting ${delay}ms before processing queue`,
128
- );
129
- setTimeout(() => this.processQueue(), delay);
72
+ async processQueue(routeKey) {
73
+ const queue = this.requestQueues.get(routeKey);
74
+ if (!queue || queue.length === 0 || this.processingRoutes.has(routeKey)) {
130
75
  return;
131
76
  }
132
77
 
133
- this.processingQueue = true;
134
-
135
- // Filter out suspended routes before processing any requests
136
- this.requestQueue = this.requestQueue.filter((request) => {
137
- const routeKey = this.getRouteKey(
138
- request.endpoint,
139
- request.options.method || "GET",
140
- );
141
- const suspendedRoute = this.suspendedRoutes.get(routeKey);
78
+ this.processingRoutes.add(routeKey);
142
79
 
143
- if (suspendedRoute && suspendedRoute.suspendedUntil > Date.now()) {
144
- // Silently resolve suspended requests
145
- console.log(
146
- `🚫 Queued request dropped: Route ${routeKey} is suspended`,
147
- );
148
- request.resolve(null);
149
- return false; // Remove from queue
150
- }
80
+ while (queue.length > 0) {
81
+ await this.waitForGlobalRateLimit();
151
82
 
152
- return true; // Keep in queue
153
- });
154
-
155
- while (this.requestQueue.length > 0) {
156
- // Double-check global rate limit for each request
157
- if (this.globalRateLimit && Date.now() < this.globalRateLimit.reset) {
158
- const delay = this.globalRateLimit.reset - Date.now();
159
- console.log(`⏳ Global rate limit active, waiting ${delay}ms`);
160
- await this.sleep(delay);
161
- this.globalRateLimit = null;
162
- }
163
-
164
- const request = this.requestQueue.shift();
83
+ const request = queue.shift();
165
84
  const { endpoint, options, resolve, reject } = request;
166
85
 
167
86
  try {
168
- const routeKey = this.getRouteKey(endpoint, options.method || "GET");
169
-
170
- // Check suspension status before processing
171
- const suspendedRoute = this.suspendedRoutes.get(routeKey);
172
- if (suspendedRoute && suspendedRoute.suspendedUntil > Date.now()) {
173
- console.log(
174
- `🚫 Processing request dropped: Route ${routeKey} is suspended`,
175
- );
176
- resolve(null);
177
- continue;
178
- }
179
-
180
- // Check rate limit before processing - suspend immediately if exhausted
87
+ // Wait for the known bucket reset instead of dropping the request.
181
88
  const rateLimit = this.rateLimits.get(routeKey);
182
89
  if (
183
90
  rateLimit &&
184
91
  rateLimit.remaining <= 0 &&
185
92
  Date.now() < rateLimit.reset
186
93
  ) {
187
- // Suspend instead of waiting
188
- const suspensionTime = 2 * 60 * 1000; // 2 minutes
189
- this.suspendedRoutes.set(routeKey, {
190
- suspendedUntil: Date.now() + suspensionTime,
191
- reason: "Rate limit exhausted during queue processing",
192
- });
193
-
194
- console.log(
195
- `⏸️ Suspending route ${routeKey} for 2 minutes (queue processing)`,
196
- );
197
-
198
- // Clear remaining queued requests for this route
199
- const clearedCount = this.clearQueuedRequestsForRoute(routeKey);
200
- if (clearedCount > 0) {
201
- console.log(
202
- `🗑️ Cleared ${clearedCount} additional queued requests for ${routeKey}`,
203
- );
204
- }
205
-
206
- resolve(null);
207
- continue;
94
+ const delay = rateLimit.reset - Date.now();
95
+ console.log(`⏳ Route ${routeKey} rate limited, waiting ${delay}ms`);
96
+ await this.sleep(delay);
97
+ this.rateLimits.delete(routeKey);
208
98
  }
209
99
 
210
100
  const result = await this.makeRequest(endpoint, options);
211
101
  resolve(result);
212
-
213
- // Delay between requests to be conservative with rate limits
214
- await this.sleep(500);
215
102
  } catch (error) {
216
103
  reject(error);
217
104
  }
218
105
  }
219
106
 
220
- this.processingQueue = false;
107
+ this.processingRoutes.delete(routeKey);
108
+ this.requestQueues.delete(routeKey);
109
+ }
110
+
111
+ /**
112
+ * Wait for a shared global rate limit to expire
113
+ * @private
114
+ */
115
+ async waitForGlobalRateLimit() {
116
+ while (this.globalRateLimit && Date.now() < this.globalRateLimit.reset) {
117
+ const delay = this.globalRateLimit.reset - Date.now();
118
+ console.log(`⏳ Global rate limit active, waiting ${delay}ms`);
119
+
120
+ if (!this.globalDelay) {
121
+ this.globalDelay = this.sleep(delay).finally(() => {
122
+ this.globalDelay = null;
123
+ if (
124
+ this.globalRateLimit &&
125
+ Date.now() >= this.globalRateLimit.reset
126
+ ) {
127
+ this.globalRateLimit = null;
128
+ }
129
+ });
130
+ }
131
+
132
+ await this.globalDelay;
133
+ }
221
134
  }
222
135
 
223
136
  /**
@@ -254,10 +167,25 @@ class RestManager {
254
167
 
255
168
  // Handle rate limiting
256
169
  if (response.status === 429) {
257
- const retryAfter =
258
- parseInt(response.headers.get("retry-after")) * 1000 || 2000;
170
+ const error = await response.json().catch(() => ({}));
171
+ const bodyRetryAfter = Number(error.retry_after);
172
+ const resetAfter = parseFloat(
173
+ response.headers.get("x-ratelimit-reset-after"),
174
+ );
175
+ const headerRetryAfter = parseFloat(
176
+ response.headers.get("retry-after"),
177
+ );
178
+ const retryAfterSeconds = Number.isFinite(bodyRetryAfter)
179
+ ? bodyRetryAfter
180
+ : Number.isFinite(resetAfter)
181
+ ? resetAfter
182
+ : headerRetryAfter;
183
+ const retryAfter = Number.isFinite(retryAfterSeconds)
184
+ ? Math.max(0, retryAfterSeconds * 1000)
185
+ : 2000;
259
186
  const isGlobal =
260
- response.headers.get("x-ratelimit-global") === "true";
187
+ response.headers.get("x-ratelimit-global") === "true" ||
188
+ error.global === true;
261
189
  const routeKey = this.getRouteKey(endpoint, options.method || "GET");
262
190
 
263
191
  console.log(
@@ -267,66 +195,24 @@ class RestManager {
267
195
  );
268
196
 
269
197
  if (isGlobal) {
270
- this.globalRateLimit = { reset: Date.now() + retryAfter + 2000 };
271
- console.log(
272
- `⏳ Global rate limit set, waiting ${retryAfter + 2000}ms`,
273
- );
198
+ this.globalRateLimit = { reset: Date.now() + retryAfter };
199
+ console.log(`⏳ Global rate limit set, waiting ${retryAfter}ms`);
274
200
  } else {
275
- const suspensionTime = retryAfter + 500;
276
- this.suspendedRoutes.set(routeKey, {
277
- suspendedUntil: Date.now() + suspensionTime,
278
- reason: `429 response on attempt ${attempt + 1}`,
279
- });
280
-
281
- console.log(
282
- `⏸️ Suspending route ${routeKey} for ${suspensionTime}ms (429 response)`,
283
- );
284
-
285
- // Clear queue for this route
286
- const clearedCount = this.clearQueuedRequestsForRoute(routeKey);
287
- if (clearedCount > 0) {
288
- console.log(
289
- `�️ Cleared ${clearedCount} queued requests for ${routeKey}`,
290
- );
291
- }
292
-
293
- // Update rate limit state
294
201
  this.rateLimits.set(routeKey, {
295
202
  remaining: 0,
296
- reset: Date.now() + suspensionTime,
203
+ reset: Date.now() + retryAfter,
297
204
  limit: this.rateLimits.get(routeKey)?.limit || 5,
298
205
  updatedAt: Date.now(),
299
206
  });
300
-
301
- // Open circuit breaker after 2 consecutive 429s
302
- const breaker = this.circuitBreakers.get(routeKey) || {
303
- failures: 0,
304
- lastFailure: 0,
305
- openUntil: 0,
306
- };
307
- breaker.failures++;
308
- breaker.lastFailure = Date.now();
309
-
310
- if (breaker.failures >= 2) {
311
- breaker.openUntil = Date.now() + suspensionTime;
312
- console.log(`🔌 Circuit breaker OPENED for ${routeKey}`);
313
- }
314
-
315
- this.circuitBreakers.set(routeKey, breaker);
316
-
317
- // Don't retry, return null immediately
318
- return null;
319
207
  }
320
208
 
321
- // For global rate limits, wait and retry
209
+ console.log(`⏳ Retrying ${routeKey} in ${retryAfter}ms`);
322
210
  if (isGlobal) {
323
- await this.sleep(retryAfter + 2000);
324
- attempt++;
325
- continue;
211
+ await this.waitForGlobalRateLimit();
212
+ } else {
213
+ await this.sleep(retryAfter);
326
214
  }
327
-
328
- // For route rate limits, don't retry
329
- return null;
215
+ continue;
330
216
  }
331
217
 
332
218
  if (!response.ok) {
@@ -343,18 +229,9 @@ class RestManager {
343
229
 
344
230
  // Handle 204 No Content (successful but no body)
345
231
  if (response.status === 204) {
346
- // Reset circuit breaker and suspension on successful response
347
- const routeKey = this.getRouteKey(endpoint, options.method || "GET");
348
- this.circuitBreakers.delete(routeKey);
349
- this.suspendedRoutes.delete(routeKey);
350
232
  return null;
351
233
  }
352
234
 
353
- // Reset circuit breaker and suspension on successful response
354
- const routeKey = this.getRouteKey(endpoint, options.method || "GET");
355
- this.circuitBreakers.delete(routeKey);
356
- this.suspendedRoutes.delete(routeKey);
357
-
358
235
  return await response.json();
359
236
  } catch (error) {
360
237
  // Do not retry on Missing Permissions (50013)
@@ -392,12 +269,20 @@ class RestManager {
392
269
 
393
270
  const remaining = parseInt(headers.get("x-ratelimit-remaining"));
394
271
  const resetTimestamp = parseFloat(headers.get("x-ratelimit-reset"));
272
+ const resetAfter = parseFloat(headers.get("x-ratelimit-reset-after"));
395
273
  const limit = parseInt(headers.get("x-ratelimit-limit"));
396
274
 
397
- if (!isNaN(remaining) && !isNaN(resetTimestamp)) {
275
+ if (!isNaN(remaining) && (!isNaN(resetAfter) || !isNaN(resetTimestamp))) {
276
+ const serverTime = Date.parse(headers.get("date"));
277
+ const clockOffset = Number.isNaN(serverTime)
278
+ ? 0
279
+ : serverTime - Date.now();
280
+
398
281
  this.rateLimits.set(routeKey, {
399
282
  remaining,
400
- reset: resetTimestamp * 1000, // Convert to milliseconds
283
+ reset: !isNaN(resetAfter)
284
+ ? Date.now() + resetAfter * 1000
285
+ : resetTimestamp * 1000 - clockOffset,
401
286
  limit,
402
287
  updatedAt: Date.now(),
403
288
  });
@@ -412,16 +297,21 @@ class RestManager {
412
297
  * @private
413
298
  */
414
299
  getRouteKey(endpoint, method) {
415
- // Normalize endpoint for rate limiting
416
- // Replace IDs with generic placeholders
417
- const normalized = endpoint
418
- .replace(/\/\d+/g, "/{id}")
419
- .replace(
420
- /\/channels\/\{id\}\/messages\/\{id\}/g,
421
- "/channels/{id}/messages/{id}",
422
- );
423
-
424
- return `${method.toUpperCase()}:${normalized}`;
300
+ const majorParameters = new Set(["channels", "guilds"]);
301
+ const segments = endpoint.split("?", 1)[0].split("/");
302
+ const normalized = [];
303
+
304
+ for (let index = 0; index < segments.length; index++) {
305
+ if (segments[index - 1] === "reactions") break;
306
+
307
+ const segment = segments[index];
308
+ const isMinorId =
309
+ /^\d{16,19}$/.test(segment) &&
310
+ !majorParameters.has(segments[index - 1]);
311
+ normalized.push(isMinorId ? "{id}" : segment);
312
+ }
313
+
314
+ return `${method.toUpperCase()}:${normalized.join("/")}`;
425
315
  }
426
316
 
427
317
  /**
@@ -435,20 +325,6 @@ class RestManager {
435
325
  this.rateLimits.delete(key);
436
326
  }
437
327
  }
438
-
439
- // Clean up expired circuit breakers
440
- for (const [key, breaker] of this.circuitBreakers.entries()) {
441
- if (now > breaker.openUntil) {
442
- this.circuitBreakers.delete(key);
443
- }
444
- }
445
-
446
- // Clean up expired route suspensions
447
- for (const [key, suspension] of this.suspendedRoutes.entries()) {
448
- if (now > suspension.suspendedUntil) {
449
- this.suspendedRoutes.delete(key);
450
- }
451
- }
452
328
  }
453
329
 
454
330
  /**
@@ -461,24 +337,6 @@ class RestManager {
461
337
  return new Promise((resolve) => setTimeout(resolve, ms));
462
338
  }
463
339
 
464
- /**
465
- * Clear queued requests for a specific route
466
- * @private
467
- * @param {string} routeKey - The route key to clear requests for
468
- * @returns {number} Number of requests cleared
469
- */
470
- clearQueuedRequestsForRoute(routeKey) {
471
- const initialLength = this.requestQueue.length;
472
- this.requestQueue = this.requestQueue.filter((request) => {
473
- const requestRouteKey = this.getRouteKey(
474
- request.endpoint,
475
- request.options.method || "GET",
476
- );
477
- return requestRouteKey !== routeKey;
478
- });
479
- return initialLength - this.requestQueue.length;
480
- }
481
-
482
340
  /**
483
341
  * Check if a route is currently rate limited
484
342
  * @param {string} endpoint - The API endpoint
@@ -509,6 +367,11 @@ class RestManager {
509
367
  getRateLimitStatus(endpoint, method = "GET") {
510
368
  const routeKey = this.getRouteKey(endpoint, method);
511
369
  const rateLimit = this.rateLimits.get(routeKey);
370
+ const routeQueueLength = this.requestQueues.get(routeKey)?.length || 0;
371
+ const queueLength = Array.from(this.requestQueues.values()).reduce(
372
+ (total, queue) => total + queue.length,
373
+ 0,
374
+ );
512
375
 
513
376
  return {
514
377
  routeKey,
@@ -529,8 +392,10 @@ class RestManager {
529
392
  resetIn: Math.max(0, rateLimit.reset - Date.now()),
530
393
  }
531
394
  : null,
532
- queueLength: this.requestQueue.length,
533
- isProcessing: this.processingQueue,
395
+ queueLength,
396
+ routeQueueLength,
397
+ isProcessing: this.processingRoutes.size > 0,
398
+ isRouteProcessing: this.processingRoutes.has(routeKey),
534
399
  };
535
400
  }
536
401
 
@@ -619,6 +484,18 @@ class RestManager {
619
484
  return updatedData;
620
485
  }
621
486
 
487
+ /**
488
+ * Delete a message from a channel
489
+ * @param {string} channelId - The channel ID
490
+ * @param {string} messageId - The message ID
491
+ * @returns {Promise<void>}
492
+ */
493
+ async deleteMessage(channelId, messageId) {
494
+ await this.request(`/channels/${channelId}/messages/${messageId}`, {
495
+ method: "DELETE",
496
+ });
497
+ }
498
+
622
499
  /**
623
500
  * React to a message
624
501
  * @param {string} channelId - The channel ID
@@ -349,6 +349,12 @@ class WebhookClient {
349
349
  if (options.threadId) {
350
350
  payload.thread_id = options.threadId;
351
351
  }
352
+ if (options.attachments !== undefined) {
353
+ payload.attachments = options.attachments;
354
+ }
355
+ if (options.files !== undefined) {
356
+ payload.files = options.files;
357
+ }
352
358
 
353
359
  return payload;
354
360
  }