discord-self-lite 0.1.5 → 0.1.7

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.5",
3
+ "version": "0.1.7",
4
4
  "description": "Lightweight Discord selfbot library",
5
5
  "keywords": [
6
6
  "discord",
@@ -90,7 +90,13 @@ class BitField {
90
90
  return bit
91
91
  .map((p) => this.resolve(p))
92
92
  .reduce((prev, p) => prev | p, BitField.defaultBit);
93
- if (typeof bit === "string") return this.FLAGS[bit];
93
+ if (typeof bit === "string") {
94
+ // Check if it's a numeric string (permission bitfield from API)
95
+ if (/^\d+$/.test(bit)) return BigInt(bit);
96
+ // Otherwise treat as flag name
97
+ return this.FLAGS[bit];
98
+ }
99
+ if (typeof bit === "number") return BigInt(bit);
94
100
  throw new TypeError("BitField.resolve: Invalid bitfield");
95
101
  }
96
102
  }
@@ -3,6 +3,18 @@
3
3
  */
4
4
  const Permissions = require("./Permissions");
5
5
 
6
+ /**
7
+ * Safe BigInt conversion
8
+ * @param {*} value - Value to convert to BigInt
9
+ * @returns {bigint} BigInt representation
10
+ */
11
+ function toBigInt(value) {
12
+ if (typeof value === "bigint") return value;
13
+ if (typeof value === "string") return BigInt(value);
14
+ if (typeof value === "number") return BigInt(value);
15
+ return 0n;
16
+ }
17
+
6
18
  class Channel {
7
19
  /**
8
20
  * Create a new Channel instance
@@ -170,7 +182,7 @@ class Channel {
170
182
  * @returns {Permissions} Permissions bitfield for this member in this channel
171
183
  */
172
184
  permissionsFor(member) {
173
- if (!member) return new Permissions(BigInt(0));
185
+ if (!member?.permissions) return new Permissions(toBigInt(0));
174
186
 
175
187
  // Start with the member's base guild permissions
176
188
  let permissions = member.permissions.bitfield;
@@ -193,8 +205,8 @@ class Channel {
193
205
  memberRoles.includes(overwrite.id) ||
194
206
  overwrite.id === this.data.guild_id
195
207
  ) {
196
- permissions &= ~BigInt(overwrite.deny || 0);
197
- permissions |= BigInt(overwrite.allow || 0);
208
+ permissions &= ~toBigInt(overwrite.deny || 0);
209
+ permissions |= toBigInt(overwrite.allow || 0);
198
210
  }
199
211
  }
200
212
  }
@@ -204,13 +216,63 @@ class Channel {
204
216
  (ow) => ow.type === 1 && ow.id === member.id,
205
217
  );
206
218
  if (memberOverwrite) {
207
- permissions &= ~BigInt(memberOverwrite.deny || 0);
208
- permissions |= BigInt(memberOverwrite.allow || 0);
219
+ permissions &= ~toBigInt(memberOverwrite.deny || 0);
220
+ permissions |= toBigInt(memberOverwrite.allow || 0);
209
221
  }
210
222
  }
211
223
 
212
224
  return new Permissions(permissions);
213
225
  }
226
+
227
+ /**
228
+ * Wait for a message in this channel
229
+ * @param {object} [options={}] - Options for awaiting messages
230
+ * @param {Function} [options.filter] - Filter function for messages (must return true/false)
231
+ * @param {number} [options.time=30000] - Timeout in milliseconds
232
+ * @param {boolean} [options.errors=true] - Whether to reject on timeout
233
+ * @returns {Promise<Message>} The matching message
234
+ * @throws {Error} If timeout is reached and errors is true
235
+ * @example
236
+ * // Wait for any message
237
+ * const msg = await channel.awaitMessage();
238
+ *
239
+ * // Wait for a message from specific user
240
+ * const msg = await channel.awaitMessage({
241
+ * filter: (m) => m.author.id === '123456789',
242
+ * time: 60000
243
+ * });
244
+ *
245
+ * // Wait for message with specific content patterns
246
+ * const msg = await channel.awaitMessage({
247
+ * filter: (m) => m.author.id === userId &&
248
+ * (m.content.includes('yes') || m.content.includes('confirm')),
249
+ * time: 30000
250
+ * });
251
+ */
252
+ async awaitMessage(options = {}) {
253
+ const { filter = () => true, time = 30000, errors = true } = options;
254
+
255
+ return new Promise((resolve, reject) => {
256
+ const listener = (message) => {
257
+ if (message.channelId === this.id && filter(message)) {
258
+ this.client.removeListener("messageCreate", listener);
259
+ clearTimeout(timeout);
260
+ resolve(message);
261
+ }
262
+ };
263
+
264
+ const timeout = setTimeout(() => {
265
+ this.client.removeListener("messageCreate", listener);
266
+ if (errors) {
267
+ reject(new Error("Message await timed out"));
268
+ } else {
269
+ resolve(null);
270
+ }
271
+ }, time);
272
+
273
+ this.client.on("messageCreate", listener);
274
+ });
275
+ }
214
276
  }
215
277
 
216
278
  module.exports = Channel;
@@ -191,6 +191,32 @@ class Client extends EventEmitter {
191
191
  return this.token;
192
192
  }
193
193
 
194
+ /**
195
+ * Get rate limit status for debugging
196
+ * @param {string} endpoint - The API endpoint
197
+ * @param {string} method - The HTTP method (default: 'GET')
198
+ * @returns {object} Rate limit information
199
+ */
200
+ getRateLimitStatus(endpoint, method = "GET") {
201
+ if (!this.rest) {
202
+ throw new Error("Client is not logged in");
203
+ }
204
+ return this.rest.getRateLimitStatus(endpoint, method);
205
+ }
206
+
207
+ /**
208
+ * Check if a route is currently rate limited
209
+ * @param {string} endpoint - The API endpoint
210
+ * @param {string} method - The HTTP method (default: 'GET')
211
+ * @returns {boolean} True if rate limited
212
+ */
213
+ isRateLimited(endpoint, method = "GET") {
214
+ if (!this.rest) {
215
+ return false;
216
+ }
217
+ return this.rest.isRateLimited(endpoint, method);
218
+ }
219
+
194
220
  /**
195
221
  * Destroy the client and clean up resources
196
222
  * @returns {void}
@@ -3,6 +3,18 @@
3
3
  */
4
4
  const Permissions = require("./Permissions");
5
5
 
6
+ /**
7
+ * Safe BigInt conversion
8
+ * @param {*} value - Value to convert to BigInt
9
+ * @returns {bigint} BigInt representation
10
+ */
11
+ function toBigInt(value) {
12
+ if (typeof value === "bigint") return value;
13
+ if (typeof value === "string") return BigInt(value);
14
+ if (typeof value === "number") return BigInt(value);
15
+ return 0n;
16
+ }
17
+
6
18
  class GuildMember {
7
19
  /**
8
20
  * Create a new GuildMember instance
@@ -58,7 +70,7 @@ class GuildMember {
58
70
  if (this._permissions) return this._permissions;
59
71
 
60
72
  // Calculate permissions based on roles
61
- let permissions = BigInt(0);
73
+ let permissions = 0n;
62
74
 
63
75
  // If member is the guild owner, they have all permissions
64
76
  if (this.id === this.guild.ownerId) {
@@ -68,15 +80,15 @@ class GuildMember {
68
80
  const everyoneRole = this.guild.roles?.find(
69
81
  (role) => role.id === this.guild.id,
70
82
  );
71
- if (everyoneRole) {
72
- permissions |= BigInt(everyoneRole.permissions || 0);
83
+ if (everyoneRole && everyoneRole.permissions != null) {
84
+ permissions |= toBigInt(everyoneRole.permissions);
73
85
  }
74
86
 
75
87
  // Add permissions from member's roles
76
88
  for (const roleId of this.roles || []) {
77
89
  const role = this.guild.roles?.find((r) => r.id === roleId);
78
- if (role) {
79
- permissions |= BigInt(role.permissions || 0);
90
+ if (role && role.permissions != null) {
91
+ permissions |= toBigInt(role.permissions);
80
92
  }
81
93
  }
82
94
  }
@@ -55,10 +55,20 @@ class RestManager {
55
55
  return;
56
56
  }
57
57
 
58
+ // Check if we're globally rate limited before starting
59
+ if (this.globalRateLimit && Date.now() < this.globalRateLimit.reset) {
60
+ const delay = this.globalRateLimit.reset - Date.now();
61
+ console.log(
62
+ `⏳ Global rate limit active, waiting ${delay}ms before processing queue`,
63
+ );
64
+ setTimeout(() => this.processQueue(), delay);
65
+ return;
66
+ }
67
+
58
68
  this.processingQueue = true;
59
69
 
60
70
  while (this.requestQueue.length > 0) {
61
- // Check global rate limit
71
+ // Double-check global rate limit for each request
62
72
  if (this.globalRateLimit && Date.now() < this.globalRateLimit.reset) {
63
73
  const delay = this.globalRateLimit.reset - Date.now();
64
74
  console.log(`⏳ Global rate limit active, waiting ${delay}ms`);
@@ -81,9 +91,17 @@ class RestManager {
81
91
  ) {
82
92
  const delay = rateLimit.reset - Date.now();
83
93
  console.log(
84
- `⏳ Route rate limit for ${routeKey}, waiting ${delay}ms`,
94
+ `⏳ Route rate limit for ${routeKey} (${endpoint}), waiting ${delay}ms`,
85
95
  );
86
96
  await this.sleep(delay);
97
+
98
+ // After waiting, check if we still have remaining requests
99
+ const updatedRateLimit = this.rateLimits.get(routeKey);
100
+ if (updatedRateLimit && updatedRateLimit.remaining <= 0) {
101
+ // Still rate limited, put request back at front of queue
102
+ this.requestQueue.unshift(request);
103
+ continue;
104
+ }
87
105
  }
88
106
 
89
107
  const result = await this.makeRequest(endpoint, options);
@@ -129,18 +147,27 @@ class RestManager {
129
147
  // Handle rate limiting
130
148
  if (response.status === 429) {
131
149
  const retryAfter =
132
- parseInt(response.headers.get("retry-after")) * 1000;
150
+ parseInt(response.headers.get("retry-after")) * 1000 + 1000;
133
151
  const isGlobal =
134
152
  response.headers.get("x-ratelimit-global") === "true";
153
+ const routeKey = this.getRouteKey(endpoint, options.method || "GET");
135
154
 
136
155
  console.log(
137
156
  `🚫 Rate limited! ${
138
- isGlobal ? "Global" : "Route"
139
- } limit, retry after ${retryAfter}ms`,
157
+ isGlobal ? "Global" : `Route (${routeKey})`
158
+ } limit for ${endpoint}, retry after ${retryAfter}ms`,
140
159
  );
141
160
 
142
161
  if (isGlobal) {
143
162
  this.globalRateLimit = { reset: Date.now() + retryAfter };
163
+ } else {
164
+ // Update route-specific rate limit for 429 responses
165
+ this.rateLimits.set(routeKey, {
166
+ remaining: 0,
167
+ reset: Date.now() + retryAfter,
168
+ limit: this.rateLimits.get(routeKey)?.limit || 5, // Default limit if unknown
169
+ updatedAt: Date.now(),
170
+ });
144
171
  }
145
172
 
146
173
  await this.sleep(retryAfter);
@@ -234,11 +261,58 @@ class RestManager {
234
261
  }
235
262
 
236
263
  /**
237
- * Sleep for a given duration
238
- * @private
264
+ * Check if a route is currently rate limited
265
+ * @param {string} endpoint - The API endpoint
266
+ * @param {string} method - The HTTP method
267
+ * @returns {boolean} True if rate limited
268
+ */
269
+ isRateLimited(endpoint, method = "GET") {
270
+ // Check global rate limit
271
+ if (this.globalRateLimit && Date.now() < this.globalRateLimit.reset) {
272
+ return true;
273
+ }
274
+
275
+ // Check route-specific rate limit
276
+ const routeKey = this.getRouteKey(endpoint, method);
277
+ const rateLimit = this.rateLimits.get(routeKey);
278
+
279
+ return (
280
+ rateLimit && rateLimit.remaining <= 0 && Date.now() < rateLimit.reset
281
+ );
282
+ }
283
+
284
+ /**
285
+ * Get rate limit status for debugging
286
+ * @param {string} endpoint - The API endpoint
287
+ * @param {string} method - The HTTP method
288
+ * @returns {object} Rate limit information
239
289
  */
240
- sleep(ms) {
241
- return new Promise((resolve) => setTimeout(resolve, ms));
290
+ getRateLimitStatus(endpoint, method = "GET") {
291
+ const routeKey = this.getRouteKey(endpoint, method);
292
+ const rateLimit = this.rateLimits.get(routeKey);
293
+
294
+ return {
295
+ routeKey,
296
+ endpoint,
297
+ method,
298
+ isGloballyLimited:
299
+ this.globalRateLimit && Date.now() < this.globalRateLimit.reset,
300
+ globalReset: this.globalRateLimit
301
+ ? new Date(this.globalRateLimit.reset)
302
+ : null,
303
+ isRouteLimited:
304
+ rateLimit && rateLimit.remaining <= 0 && Date.now() < rateLimit.reset,
305
+ routeLimit: rateLimit
306
+ ? {
307
+ remaining: rateLimit.remaining,
308
+ limit: rateLimit.limit,
309
+ reset: new Date(rateLimit.reset),
310
+ resetIn: Math.max(0, rateLimit.reset - Date.now()),
311
+ }
312
+ : null,
313
+ queueLength: this.requestQueue.length,
314
+ isProcessing: this.processingQueue,
315
+ };
242
316
  }
243
317
 
244
318
  /**
@@ -1,5 +1,24 @@
1
1
  const DiscordAPIError = require("../../classes/DiscordAPIError");
2
2
 
3
+ /**
4
+ * Convert a color value to Discord's expected integer format
5
+ * @param {*} color - Color value (hex string, number, etc.)
6
+ * @returns {number|null} Integer color value or null
7
+ */
8
+ function resolveColor(color) {
9
+ if (color === null || color === undefined) return null;
10
+ if (typeof color === "number") return color;
11
+ if (typeof color === "string") {
12
+ // Handle hex colors
13
+ if (color.startsWith("#")) {
14
+ return parseInt(color.slice(1), 16);
15
+ }
16
+ // Handle other string formats if needed
17
+ return parseInt(color, 16);
18
+ }
19
+ return null;
20
+ }
21
+
3
22
  class WebhookClient {
4
23
  /**
5
24
  * Create a new WebhookClient
@@ -186,7 +205,10 @@ class WebhookClient {
186
205
  payload.avatar_url = options.avatarURL;
187
206
  }
188
207
  if (options.embeds) {
189
- payload.embeds = options.embeds;
208
+ payload.embeds = options.embeds.map((embed) => ({
209
+ ...embed,
210
+ color: resolveColor(embed.color),
211
+ }));
190
212
  }
191
213
  if (options.tts !== undefined) {
192
214
  payload.tts = options.tts;
@@ -212,11 +234,11 @@ class WebhookClient {
212
234
  description: data.description || null,
213
235
  url: data.url || null,
214
236
  timestamp: data.timestamp || null,
215
- color: data.color || null,
237
+ color: resolveColor(data.color),
216
238
  footer: data.footer
217
239
  ? {
218
240
  text: data.footer.text,
219
- icon_url: data.footer.iconURL,
241
+ icon_url: data.footer.iconURL || data.footer.icon_url,
220
242
  }
221
243
  : null,
222
244
  image: data.image ? { url: data.image } : null,
@@ -225,7 +247,7 @@ class WebhookClient {
225
247
  ? {
226
248
  name: data.author.name,
227
249
  url: data.author.url,
228
- icon_url: data.author.iconURL,
250
+ icon_url: data.author.iconURL || data.author.icon_url,
229
251
  }
230
252
  : null,
231
253
  fields: data.fields || [],
package/src/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * discord-self-lite - A lightweight Discord selfbot library
3
3
  * @module discord-self-lite
4
- * @version 0.1.0
4
+ * @version 0.1.7
5
5
  * @description A modern, lightweight Discord selfbot library with webhook support, built for Node.js 22+ with zero external dependencies (except ws for WebSocket connections).
6
6
  *
7
7
  * @example