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 +1 -1
- package/src/classes/BitField.js +7 -1
- package/src/classes/Channel.js +67 -5
- package/src/classes/Client.js +26 -0
- package/src/classes/GuildMember.js +17 -5
- package/src/connection/rest/RestManager.js +83 -9
- package/src/connection/webhook/WebhookClient.js +26 -4
- package/src/index.js +1 -1
package/package.json
CHANGED
package/src/classes/BitField.js
CHANGED
|
@@ -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")
|
|
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
|
}
|
package/src/classes/Channel.js
CHANGED
|
@@ -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(
|
|
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 &= ~
|
|
197
|
-
permissions |=
|
|
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 &= ~
|
|
208
|
-
permissions |=
|
|
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;
|
package/src/classes/Client.js
CHANGED
|
@@ -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 =
|
|
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 |=
|
|
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 |=
|
|
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
|
-
//
|
|
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" :
|
|
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
|
-
*
|
|
238
|
-
* @
|
|
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
|
-
|
|
241
|
-
|
|
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
|
|
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.
|
|
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
|