discord-sb.js 1.0.5 → 1.0.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/README.md +8 -8
- package/package.json +4 -4
- package/src/client/Client.js +40 -3
- package/src/client/websocket/WebSocketManager.js +44 -24
- package/src/managers/CachedManager.js +7 -7
- package/src/managers/QuestManager.js +24 -6
- package/src/rest/RequestHandler.js +62 -51
- package/src/structures/Guild.js +10 -7
- package/src/structures/Presence.js +9 -1
- package/src/util/Sweepers.js +57 -30
- package/typings/enums.d.ts +1 -1
package/README.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
[!IMPORTANT]
|
|
2
|
+
**This project is a fork of the [discord.js-selfbot-v13](https://github.com/aiko-chan-ai/discord.js-selfbot-v13) archived project.**
|
|
3
3
|
|
|
4
4
|
# discord.js-selfbot-v13 (fork)
|
|
5
5
|
|
|
6
6
|
Small additions focused on profile data and account integrations.
|
|
7
7
|
|
|
8
|
-
##
|
|
9
|
-
-
|
|
10
|
-
- `client.user.fetchConnections({ includeMetadata })`
|
|
8
|
+
## What's New
|
|
9
|
+
- User fetches now include bio and pronouns when available (via `/users/{id}/profile`).
|
|
10
|
+
- `client.user.fetchConnections({ includeMetadata })` returns your connections (Spotify, Steam, etc.) with metadata when the API exposes it.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Quick Examples
|
|
13
13
|
```js
|
|
14
|
-
//
|
|
14
|
+
// Fetch a user's bio and pronouns
|
|
15
15
|
const user = await client.users.fetch('123456789012345678');
|
|
16
16
|
console.log(user.bio, user.pronouns);
|
|
17
17
|
|
|
18
|
-
//
|
|
18
|
+
// List your connections with metadata
|
|
19
19
|
const connections = await client.user.fetchConnections();
|
|
20
20
|
console.log(connections);
|
|
21
21
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discord-sb.js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "An unofficial discord.js fork for creating selfbots",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"types": "./typings/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/sqlu/discord-
|
|
32
|
+
"url": "git+https://github.com/sqlu/discord-sb.js.git"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"discord.js",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"author": "Snayz",
|
|
48
48
|
"license": "GNU General Public License v3.0",
|
|
49
49
|
"bugs": {
|
|
50
|
-
"url": "https://github.com/sqlu/discord-
|
|
50
|
+
"url": "https://github.com/sqlu/discord-sb.js/issues"
|
|
51
51
|
},
|
|
52
|
-
"homepage": "https://github.com/sqlu/discord-
|
|
52
|
+
"homepage": "https://github.com/sqlu/discord-sb.js#readme",
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@discordjs/builders": "^1.6.3",
|
|
55
55
|
"@discordjs/collection": "^2.1.1",
|
package/src/client/Client.js
CHANGED
|
@@ -40,6 +40,7 @@ const DataResolver = require('../util/DataResolver');
|
|
|
40
40
|
const Intents = require('../util/Intents');
|
|
41
41
|
const DiscordAuthWebsocket = require('../util/RemoteAuth');
|
|
42
42
|
const Sweepers = require('../util/Sweepers');
|
|
43
|
+
const TOKEN_PREFIX_REGEX = /^(Bot|Bearer)\s*/i;
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* The main hub for interacting with the Discord API, and the starting point for any bot.
|
|
@@ -184,6 +185,19 @@ class Client extends BaseClient {
|
|
|
184
185
|
*/
|
|
185
186
|
this.settings = new ClientUserSettingManager(this);
|
|
186
187
|
|
|
188
|
+
this._emojiCacheDirty = true;
|
|
189
|
+
this._emojiManager = null;
|
|
190
|
+
this._invalidateEmojiCache = () => {
|
|
191
|
+
this._emojiCacheDirty = true;
|
|
192
|
+
};
|
|
193
|
+
this.on(Events.GUILD_CREATE, this._invalidateEmojiCache);
|
|
194
|
+
this.on(Events.GUILD_DELETE, this._invalidateEmojiCache);
|
|
195
|
+
this.on(Events.GUILD_EMOJI_CREATE, this._invalidateEmojiCache);
|
|
196
|
+
this.on(Events.GUILD_EMOJI_DELETE, this._invalidateEmojiCache);
|
|
197
|
+
this.on(Events.GUILD_EMOJI_UPDATE, this._invalidateEmojiCache);
|
|
198
|
+
this.on(Events.GUILD_EMOJIS_UPDATE, this._invalidateEmojiCache);
|
|
199
|
+
this.on(Events.GUILD_UNAVAILABLE, this._invalidateEmojiCache);
|
|
200
|
+
|
|
187
201
|
Object.defineProperty(this, 'token', { writable: true });
|
|
188
202
|
if (!this.token && 'DISCORD_TOKEN' in process.env) {
|
|
189
203
|
/**
|
|
@@ -240,9 +254,20 @@ class Client extends BaseClient {
|
|
|
240
254
|
* @readonly
|
|
241
255
|
*/
|
|
242
256
|
get emojis() {
|
|
257
|
+
if (this._emojiCacheDirty || !this._emojiManager) {
|
|
258
|
+
this._emojiManager = this._buildEmojiCache();
|
|
259
|
+
this._emojiCacheDirty = false;
|
|
260
|
+
}
|
|
261
|
+
return this._emojiManager;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
_buildEmojiCache() {
|
|
243
265
|
const emojis = new BaseGuildEmojiManager(this);
|
|
244
266
|
for (const guild of this.guilds.cache.values()) {
|
|
245
|
-
if (guild.available)
|
|
267
|
+
if (!guild.available) continue;
|
|
268
|
+
for (const emoji of guild.emojis.cache.values()) {
|
|
269
|
+
emojis.cache.set(emoji.id, emoji);
|
|
270
|
+
}
|
|
246
271
|
}
|
|
247
272
|
return emojis;
|
|
248
273
|
}
|
|
@@ -274,7 +299,7 @@ class Client extends BaseClient {
|
|
|
274
299
|
*/
|
|
275
300
|
async login(token = this.token) {
|
|
276
301
|
if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID');
|
|
277
|
-
this.token = token = token.replace(
|
|
302
|
+
this.token = token = token.replace(TOKEN_PREFIX_REGEX, '');
|
|
278
303
|
this.emit(
|
|
279
304
|
Events.DEBUG,
|
|
280
305
|
`
|
|
@@ -363,6 +388,16 @@ class Client extends BaseClient {
|
|
|
363
388
|
destroy() {
|
|
364
389
|
super.destroy();
|
|
365
390
|
|
|
391
|
+
if (this._invalidateEmojiCache) {
|
|
392
|
+
this.off(Events.GUILD_CREATE, this._invalidateEmojiCache);
|
|
393
|
+
this.off(Events.GUILD_DELETE, this._invalidateEmojiCache);
|
|
394
|
+
this.off(Events.GUILD_EMOJI_CREATE, this._invalidateEmojiCache);
|
|
395
|
+
this.off(Events.GUILD_EMOJI_DELETE, this._invalidateEmojiCache);
|
|
396
|
+
this.off(Events.GUILD_EMOJI_UPDATE, this._invalidateEmojiCache);
|
|
397
|
+
this.off(Events.GUILD_EMOJIS_UPDATE, this._invalidateEmojiCache);
|
|
398
|
+
this.off(Events.GUILD_UNAVAILABLE, this._invalidateEmojiCache);
|
|
399
|
+
}
|
|
400
|
+
|
|
366
401
|
for (const fn of this._cleanups) fn();
|
|
367
402
|
this._cleanups.clear();
|
|
368
403
|
|
|
@@ -370,6 +405,8 @@ class Client extends BaseClient {
|
|
|
370
405
|
|
|
371
406
|
this.sweepers.destroy();
|
|
372
407
|
this.ws.destroy();
|
|
408
|
+
this._emojiManager = null;
|
|
409
|
+
this._emojiCacheDirty = true;
|
|
373
410
|
this.token = null;
|
|
374
411
|
}
|
|
375
412
|
|
|
@@ -841,7 +878,7 @@ class Client extends BaseClient {
|
|
|
841
878
|
* @returns {Promise<boolean>}
|
|
842
879
|
*/
|
|
843
880
|
async installUserApps(applicationId, scopes = ['applications.commands']) {
|
|
844
|
-
const scope =
|
|
881
|
+
const scope = typeof scopes === 'string' ? scopes : scopes.join(' ');
|
|
845
882
|
await this.api.oauth2.authorize.post({
|
|
846
883
|
query: {
|
|
847
884
|
client_id: applicationId,
|
|
@@ -79,6 +79,7 @@ class WebSocketManager extends EventEmitter {
|
|
|
79
79
|
* @name WebSocketManager#packetQueue
|
|
80
80
|
*/
|
|
81
81
|
Object.defineProperty(this, 'packetQueue', { value: [] });
|
|
82
|
+
this._processingQueue = false;
|
|
82
83
|
|
|
83
84
|
/**
|
|
84
85
|
* The current status of this WebSocketManager
|
|
@@ -107,8 +108,11 @@ class WebSocketManager extends EventEmitter {
|
|
|
107
108
|
* @readonly
|
|
108
109
|
*/
|
|
109
110
|
get ping() {
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
let total = 0;
|
|
112
|
+
for (const shard of this.shards.values()) {
|
|
113
|
+
total += shard.ping;
|
|
114
|
+
}
|
|
115
|
+
return this.shards.size ? total / this.shards.size : 0;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
/**
|
|
@@ -315,8 +319,37 @@ class WebSocketManager extends EventEmitter {
|
|
|
315
319
|
if (this.destroyed) return;
|
|
316
320
|
this.debug(`Manager was destroyed. Called by:\n${new Error('MANAGER_DESTROYED').stack}`);
|
|
317
321
|
this.destroyed = true;
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
this.shardQueue.clear();
|
|
323
|
+
for (const shard of this.shards.values()) shard.destroy({ closeCode: 1_000, reset: true, emit: false, log: false });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
_dispatchPacket(packet, shard) {
|
|
327
|
+
if (packet && PacketHandlers[packet.t]) {
|
|
328
|
+
PacketHandlers[packet.t](this.client, packet, shard);
|
|
329
|
+
} else if (packet) {
|
|
330
|
+
/**
|
|
331
|
+
* Emitted whenever a packet isn't handled.
|
|
332
|
+
* @event Client#unhandledPacket
|
|
333
|
+
* @param {Object} packet The packet (t: EVENT_NAME, d: any)
|
|
334
|
+
* @param {Number} shard The shard that received the packet (Shard 0)
|
|
335
|
+
*/
|
|
336
|
+
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
_schedulePacketQueue() {
|
|
341
|
+
if (this._processingQueue || this.status !== Status.READY || !this.packetQueue.length) return;
|
|
342
|
+
this._processingQueue = true;
|
|
343
|
+
setImmediate(() => {
|
|
344
|
+
while (this.packetQueue.length && this.status === Status.READY) {
|
|
345
|
+
const { packet, shard } = this.packetQueue.shift();
|
|
346
|
+
this._dispatchPacket(packet, shard);
|
|
347
|
+
}
|
|
348
|
+
this._processingQueue = false;
|
|
349
|
+
if (this.packetQueue.length && this.status === Status.READY) {
|
|
350
|
+
this._schedulePacketQueue();
|
|
351
|
+
}
|
|
352
|
+
}).unref();
|
|
320
353
|
}
|
|
321
354
|
|
|
322
355
|
/**
|
|
@@ -327,30 +360,17 @@ class WebSocketManager extends EventEmitter {
|
|
|
327
360
|
* @private
|
|
328
361
|
*/
|
|
329
362
|
handlePacket(packet, shard) {
|
|
330
|
-
if (packet && this.status !== Status.READY) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
363
|
+
if (packet && this.status !== Status.READY && !BeforeReadyWhitelist.includes(packet.t)) {
|
|
364
|
+
this.packetQueue.push({ packet, shard });
|
|
365
|
+
return false;
|
|
335
366
|
}
|
|
336
367
|
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
setImmediate(() => {
|
|
340
|
-
this.handlePacket(item.packet, item.shard);
|
|
341
|
-
}).unref();
|
|
368
|
+
if (packet) {
|
|
369
|
+
this._dispatchPacket(packet, shard);
|
|
342
370
|
}
|
|
343
371
|
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
-
} else if (packet) {
|
|
347
|
-
/**
|
|
348
|
-
* Emitted whenever a packet isn't handled.
|
|
349
|
-
* @event Client#unhandledPacket
|
|
350
|
-
* @param {Object} packet The packet (t: EVENT_NAME, d: any)
|
|
351
|
-
* @param {Number} shard The shard that received the packet (Shard 0)
|
|
352
|
-
*/
|
|
353
|
-
this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
|
|
372
|
+
if (this.status === Status.READY && this.packetQueue.length) {
|
|
373
|
+
this._schedulePacketQueue();
|
|
354
374
|
}
|
|
355
375
|
|
|
356
376
|
return true;
|
|
@@ -51,19 +51,19 @@ class CachedManager extends DataManager {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
_add(data, cache = true, { id, extras = [] } = {}) {
|
|
54
|
-
const
|
|
54
|
+
const entryId = id ?? data.id;
|
|
55
|
+
const existing = this.cache.get(entryId);
|
|
55
56
|
if (existing) {
|
|
56
|
-
if (
|
|
57
|
+
if (typeof existing._patch === 'function') {
|
|
57
58
|
existing._patch(data);
|
|
58
|
-
return existing;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return clone;
|
|
60
|
+
if (cache) this.cache.set(entryId, existing);
|
|
61
|
+
return existing;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
const entry = this.holds ? new this.holds(this.client, data, ...extras) : data;
|
|
66
|
-
|
|
65
|
+
const cacheId = entryId ?? entry.id;
|
|
66
|
+
if (cache) this.cache.set(cacheId, entry);
|
|
67
67
|
return entry;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -131,7 +131,11 @@ class QuestManager extends BaseManager {
|
|
|
131
131
|
* @returns {Quest[]}
|
|
132
132
|
*/
|
|
133
133
|
getExpired(date = new Date()) {
|
|
134
|
-
|
|
134
|
+
const expired = [];
|
|
135
|
+
for (const quest of this.cache.values()) {
|
|
136
|
+
if (quest.isExpired(date)) expired.push(quest);
|
|
137
|
+
}
|
|
138
|
+
return expired;
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
/**
|
|
@@ -139,7 +143,11 @@ class QuestManager extends BaseManager {
|
|
|
139
143
|
* @returns {Quest[]}
|
|
140
144
|
*/
|
|
141
145
|
getCompleted() {
|
|
142
|
-
|
|
146
|
+
const completed = [];
|
|
147
|
+
for (const quest of this.cache.values()) {
|
|
148
|
+
if (quest.isCompleted()) completed.push(quest);
|
|
149
|
+
}
|
|
150
|
+
return completed;
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
/**
|
|
@@ -147,7 +155,13 @@ class QuestManager extends BaseManager {
|
|
|
147
155
|
* @returns {Quest[]}
|
|
148
156
|
*/
|
|
149
157
|
getClaimable() {
|
|
150
|
-
|
|
158
|
+
const claimable = [];
|
|
159
|
+
for (const quest of this.cache.values()) {
|
|
160
|
+
if (quest.isCompleted() && !quest.hasClaimedRewards()) {
|
|
161
|
+
claimable.push(quest);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return claimable;
|
|
151
165
|
}
|
|
152
166
|
|
|
153
167
|
/**
|
|
@@ -155,9 +169,13 @@ class QuestManager extends BaseManager {
|
|
|
155
169
|
* @returns {Quest[]}
|
|
156
170
|
*/
|
|
157
171
|
filterQuestsValid() {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
172
|
+
const valid = [];
|
|
173
|
+
for (const quest of this.cache.values()) {
|
|
174
|
+
if (quest.id !== '1412491570820812933' && !quest.isCompleted() && !quest.isExpired()) {
|
|
175
|
+
valid.push(quest);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return valid;
|
|
161
179
|
}
|
|
162
180
|
|
|
163
181
|
/**
|
|
@@ -75,13 +75,38 @@ class RequestHandler {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
get limited() {
|
|
78
|
-
return this.
|
|
78
|
+
return this._getActiveRateLimit() !== null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
get _inactive() {
|
|
82
82
|
return this.queue.remaining === 0 && !this.limited;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
_getActiveRateLimit(now = Date.now()) {
|
|
86
|
+
if (this.manager.globalRemaining <= 0 && now < this.manager.globalReset) {
|
|
87
|
+
const timeout = this.manager.globalReset + this.manager.client.options.restTimeOffset - now;
|
|
88
|
+
return { isGlobal: true, limit: this.manager.globalLimit, timeout };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.remaining <= 0 && now < this.reset) {
|
|
92
|
+
const timeout = this.reset + this.manager.client.options.restTimeOffset - now;
|
|
93
|
+
return { isGlobal: false, limit: this.limit, timeout };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_getDelayPromise(isGlobal, timeout) {
|
|
100
|
+
const delay = Math.max(timeout, 0);
|
|
101
|
+
if (isGlobal) {
|
|
102
|
+
if (!this.manager.globalDelay) {
|
|
103
|
+
this.manager.globalDelay = this.globalDelayFor(delay);
|
|
104
|
+
}
|
|
105
|
+
return this.manager.globalDelay;
|
|
106
|
+
}
|
|
107
|
+
return sleep(delay);
|
|
108
|
+
}
|
|
109
|
+
|
|
85
110
|
globalDelayFor(ms) {
|
|
86
111
|
return new Promise(resolve => {
|
|
87
112
|
setTimeout(() => {
|
|
@@ -116,32 +141,29 @@ class RequestHandler {
|
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
async execute(request, captchaKey, captchaToken) {
|
|
144
|
+
const hasRateLimitListener = this.manager.client.listenerCount(RATE_LIMIT) > 0;
|
|
145
|
+
const hasApiRequestListener = this.manager.client.listenerCount(API_REQUEST) > 0;
|
|
146
|
+
const hasApiResponseListener = this.manager.client.listenerCount(API_RESPONSE) > 0;
|
|
147
|
+
const invalidRequestInterval = this.manager.client.options.invalidRequestWarningInterval;
|
|
148
|
+
const hasInvalidRequestListener =
|
|
149
|
+
this.manager.client.listenerCount(INVALID_REQUEST_WARNING) > 0 && invalidRequestInterval > 0;
|
|
150
|
+
|
|
119
151
|
/*
|
|
120
152
|
* After calculations have been done, pre-emptively stop further requests
|
|
121
153
|
* Potentially loop until this task can run if e.g. the global rate limit is hit twice
|
|
122
154
|
*/
|
|
123
|
-
|
|
124
|
-
const isGlobal =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (isGlobal) {
|
|
128
|
-
// Set the variables based on the global rate limit
|
|
129
|
-
limit = this.manager.globalLimit;
|
|
130
|
-
timeout = this.manager.globalReset + this.manager.client.options.restTimeOffset - Date.now();
|
|
131
|
-
} else {
|
|
132
|
-
// Set the variables based on the route-specific rate limit
|
|
133
|
-
limit = this.limit;
|
|
134
|
-
timeout = this.reset + this.manager.client.options.restTimeOffset - Date.now();
|
|
135
|
-
}
|
|
155
|
+
for (let rateLimitState = this._getActiveRateLimit(); rateLimitState; rateLimitState = this._getActiveRateLimit()) {
|
|
156
|
+
const { isGlobal, limit, timeout } = rateLimitState;
|
|
157
|
+
const safeTimeout = Math.max(timeout, 0);
|
|
136
158
|
|
|
137
|
-
if (
|
|
159
|
+
if (hasRateLimitListener) {
|
|
138
160
|
/**
|
|
139
161
|
* Emitted when the client hits a rate limit while making a request
|
|
140
162
|
* @event BaseClient#rateLimit
|
|
141
163
|
* @param {RateLimitData} rateLimitData Object containing the rate limit info
|
|
142
164
|
*/
|
|
143
165
|
this.manager.client.emit(RATE_LIMIT, {
|
|
144
|
-
timeout,
|
|
166
|
+
timeout: safeTimeout,
|
|
145
167
|
limit,
|
|
146
168
|
method: request.method,
|
|
147
169
|
path: request.path,
|
|
@@ -150,27 +172,19 @@ class RequestHandler {
|
|
|
150
172
|
});
|
|
151
173
|
}
|
|
152
174
|
|
|
153
|
-
|
|
154
|
-
// If this is the first task to reach the global timeout, set the global delay
|
|
155
|
-
if (!this.manager.globalDelay) {
|
|
156
|
-
// The global delay function should clear the global delay state when it is resolved
|
|
157
|
-
this.manager.globalDelay = this.globalDelayFor(timeout);
|
|
158
|
-
}
|
|
159
|
-
delayPromise = this.manager.globalDelay;
|
|
160
|
-
} else {
|
|
161
|
-
delayPromise = sleep(timeout);
|
|
162
|
-
}
|
|
175
|
+
const delayPromise = this._getDelayPromise(isGlobal, safeTimeout);
|
|
163
176
|
|
|
164
177
|
// Determine whether a RateLimitError should be thrown
|
|
165
|
-
await this.onRateLimit(request, limit,
|
|
178
|
+
await this.onRateLimit(request, limit, safeTimeout, isGlobal); // eslint-disable-line no-await-in-loop
|
|
166
179
|
|
|
167
180
|
// Wait for the timeout to expire in order to avoid an actual 429
|
|
168
181
|
await delayPromise; // eslint-disable-line no-await-in-loop
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
// As the request goes out, update the global usage information
|
|
172
|
-
|
|
173
|
-
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
if (!this.manager.globalReset || this.manager.globalReset < now) {
|
|
187
|
+
this.manager.globalReset = now + 1_000;
|
|
174
188
|
this.manager.globalRemaining = this.manager.globalLimit;
|
|
175
189
|
}
|
|
176
190
|
this.manager.globalRemaining--;
|
|
@@ -185,7 +199,7 @@ class RequestHandler {
|
|
|
185
199
|
* @property {number} retries The number of times this request has been attempted
|
|
186
200
|
*/
|
|
187
201
|
|
|
188
|
-
if (
|
|
202
|
+
if (hasApiRequestListener) {
|
|
189
203
|
/**
|
|
190
204
|
* Emitted before every API request.
|
|
191
205
|
* This event can emit several times for the same request, e.g. when hitting a rate limit.
|
|
@@ -217,7 +231,7 @@ class RequestHandler {
|
|
|
217
231
|
return this.execute(request);
|
|
218
232
|
}
|
|
219
233
|
|
|
220
|
-
if (
|
|
234
|
+
if (hasApiResponseListener) {
|
|
221
235
|
/**
|
|
222
236
|
* Emitted after every API request has received a response.
|
|
223
237
|
* This event does not necessarily correlate to completion of the request, e.g. when hitting a rate limit.
|
|
@@ -278,16 +292,14 @@ class RequestHandler {
|
|
|
278
292
|
|
|
279
293
|
// Count the invalid requests
|
|
280
294
|
if (res.status === 401 || res.status === 403 || res.status === 429) {
|
|
281
|
-
|
|
282
|
-
|
|
295
|
+
const invalidNow = Date.now();
|
|
296
|
+
if (!invalidCountResetTime || invalidCountResetTime < invalidNow) {
|
|
297
|
+
invalidCountResetTime = invalidNow + 1_000 * 60 * 10;
|
|
283
298
|
invalidCount = 0;
|
|
284
299
|
}
|
|
285
300
|
invalidCount++;
|
|
286
301
|
|
|
287
|
-
const emitInvalid =
|
|
288
|
-
this.manager.client.listenerCount(INVALID_REQUEST_WARNING) &&
|
|
289
|
-
this.manager.client.options.invalidRequestWarningInterval > 0 &&
|
|
290
|
-
invalidCount % this.manager.client.options.invalidRequestWarningInterval === 0;
|
|
302
|
+
const emitInvalid = hasInvalidRequestListener && invalidCount % invalidRequestInterval === 0;
|
|
291
303
|
if (emitInvalid) {
|
|
292
304
|
/**
|
|
293
305
|
* @typedef {Object} InvalidRequestWarningData
|
|
@@ -303,7 +315,7 @@ class RequestHandler {
|
|
|
303
315
|
*/
|
|
304
316
|
this.manager.client.emit(INVALID_REQUEST_WARNING, {
|
|
305
317
|
count: invalidCount,
|
|
306
|
-
remainingTime: invalidCountResetTime -
|
|
318
|
+
remainingTime: invalidCountResetTime - invalidNow,
|
|
307
319
|
});
|
|
308
320
|
}
|
|
309
321
|
}
|
|
@@ -318,17 +330,16 @@ class RequestHandler {
|
|
|
318
330
|
if (res.status >= 400 && res.status < 500) {
|
|
319
331
|
// Handle ratelimited requests
|
|
320
332
|
if (res.status === 429) {
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
timeout
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
333
|
+
const rateLimitNow = Date.now();
|
|
334
|
+
const rateLimitState = this._getActiveRateLimit(rateLimitNow);
|
|
335
|
+
const isGlobal = rateLimitState?.isGlobal ?? this.globalLimited;
|
|
336
|
+
const limit = rateLimitState?.limit ?? (isGlobal ? this.manager.globalLimit : this.limit);
|
|
337
|
+
const computedTimeout =
|
|
338
|
+
rateLimitState?.timeout ??
|
|
339
|
+
(isGlobal
|
|
340
|
+
? this.manager.globalReset + this.manager.client.options.restTimeOffset - rateLimitNow
|
|
341
|
+
: this.reset + this.manager.client.options.restTimeOffset - rateLimitNow);
|
|
342
|
+
const safeTimeout = Math.max(computedTimeout, 0);
|
|
332
343
|
|
|
333
344
|
this.manager.client.emit(
|
|
334
345
|
DEBUG,
|
|
@@ -338,11 +349,11 @@ class RequestHandler {
|
|
|
338
349
|
Path : ${request.path}
|
|
339
350
|
Route : ${request.route}
|
|
340
351
|
Limit : ${limit}
|
|
341
|
-
Timeout : ${
|
|
352
|
+
Timeout : ${safeTimeout}ms
|
|
342
353
|
Sublimit: ${sublimitTimeout ? `${sublimitTimeout}ms` : 'None'}`,
|
|
343
354
|
);
|
|
344
355
|
|
|
345
|
-
await this.onRateLimit(request, limit,
|
|
356
|
+
await this.onRateLimit(request, limit, safeTimeout, isGlobal);
|
|
346
357
|
|
|
347
358
|
// If caused by a sublimit, wait it out here so other requests on the route can be handled
|
|
348
359
|
if (sublimitTimeout) {
|
package/src/structures/Guild.js
CHANGED
|
@@ -1670,16 +1670,19 @@ class Guild extends AnonymousGuild {
|
|
|
1670
1670
|
* guild.markRead([
|
|
1671
1671
|
* { channel_id: '123456789012345678', message_id: '987654321098765432' }
|
|
1672
1672
|
* ]);
|
|
1673
|
-
|
|
1673
|
+
*/
|
|
1674
1674
|
async markRead(readStates = []) {
|
|
1675
1675
|
// If no readStates provided, get all channels in guild
|
|
1676
1676
|
if (readStates.length === 0) {
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1677
|
+
readStates = [];
|
|
1678
|
+
for (const channel of this.channels.cache.values()) {
|
|
1679
|
+
if (!channel.isTextBased()) continue;
|
|
1680
|
+
readStates.push({
|
|
1681
|
+
channel_id: channel.id,
|
|
1682
|
+
message_id: channel.lastMessageId || '0',
|
|
1683
|
+
read_state_type: 0,
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1683
1686
|
}
|
|
1684
1687
|
|
|
1685
1688
|
const data = await this.client.api['read-states']['ack-bulk'].post({
|
|
@@ -142,7 +142,15 @@ class Presence extends Base {
|
|
|
142
142
|
|
|
143
143
|
_clone() {
|
|
144
144
|
const clone = Object.assign(Object.create(this), this);
|
|
145
|
-
|
|
145
|
+
if (!this.activities.length) {
|
|
146
|
+
clone.activities = this.activities;
|
|
147
|
+
return clone;
|
|
148
|
+
}
|
|
149
|
+
const clonedActivities = [];
|
|
150
|
+
for (const activity of this.activities) {
|
|
151
|
+
clonedActivities.push(typeof activity._clone === 'function' ? activity._clone() : activity);
|
|
152
|
+
}
|
|
153
|
+
clone.activities = clonedActivities;
|
|
146
154
|
return clone;
|
|
147
155
|
}
|
|
148
156
|
|
package/src/util/Sweepers.js
CHANGED
|
@@ -33,29 +33,22 @@ class Sweepers {
|
|
|
33
33
|
* A record of interval timeout that is used to sweep the indicated items, or null if not being swept
|
|
34
34
|
* @type {Object<SweeperKey, ?Timeout>}
|
|
35
35
|
*/
|
|
36
|
-
this.intervals =
|
|
36
|
+
this.intervals = {};
|
|
37
|
+
for (const key of SweeperKeys) {
|
|
38
|
+
this.intervals[key] = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Cached validated options to avoid recalculating filters
|
|
43
|
+
* @type {Map<string, Object>}
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
this._validatedOptions = new Map();
|
|
37
47
|
|
|
38
48
|
for (const key of SweeperKeys) {
|
|
39
49
|
if (!(key in options)) continue;
|
|
40
50
|
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
const clonedOptions = { ...this.options[key] };
|
|
44
|
-
|
|
45
|
-
// Handle cases that have a "lifetime"
|
|
46
|
-
if (!('filter' in clonedOptions)) {
|
|
47
|
-
switch (key) {
|
|
48
|
-
case 'invites':
|
|
49
|
-
clonedOptions.filter = this.constructor.expiredInviteSweepFilter(clonedOptions.lifetime);
|
|
50
|
-
break;
|
|
51
|
-
case 'messages':
|
|
52
|
-
clonedOptions.filter = this.constructor.outdatedMessageSweepFilter(clonedOptions.lifetime);
|
|
53
|
-
break;
|
|
54
|
-
case 'threads':
|
|
55
|
-
clonedOptions.filter = this.constructor.archivedThreadSweepFilter(clonedOptions.lifetime);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
51
|
+
const clonedOptions = this._getValidatedOptions(key);
|
|
59
52
|
this._initInterval(key, `sweep${key[0].toUpperCase()}${key.slice(1)}`, clonedOptions);
|
|
60
53
|
}
|
|
61
54
|
}
|
|
@@ -124,6 +117,14 @@ class Sweepers {
|
|
|
124
117
|
return this._sweepGuildDirectProp('members', filter, { outputName: 'guild members' }).items;
|
|
125
118
|
}
|
|
126
119
|
|
|
120
|
+
_getTextChannels() {
|
|
121
|
+
const textChannels = [];
|
|
122
|
+
for (const channel of this.client.channels.cache.values()) {
|
|
123
|
+
if (channel.isText()) textChannels.push(channel);
|
|
124
|
+
}
|
|
125
|
+
return textChannels;
|
|
126
|
+
}
|
|
127
|
+
|
|
127
128
|
/**
|
|
128
129
|
* Sweeps all text-based channels' messages and removes the ones which are indicated by the filter.
|
|
129
130
|
* @param {Function} filter The function used to determine which messages will be removed from the caches.
|
|
@@ -142,15 +143,13 @@ class Sweepers {
|
|
|
142
143
|
if (typeof filter !== 'function') {
|
|
143
144
|
throw new TypeError('INVALID_TYPE', 'filter', 'function');
|
|
144
145
|
}
|
|
145
|
-
|
|
146
|
+
const textChannels = this._getTextChannels();
|
|
146
147
|
let messages = 0;
|
|
147
148
|
|
|
148
|
-
for (const channel of
|
|
149
|
-
if (!channel.isText()) continue;
|
|
150
|
-
|
|
151
|
-
channels++;
|
|
149
|
+
for (const channel of textChannels) {
|
|
152
150
|
messages += channel.messages.cache.sweep(filter);
|
|
153
151
|
}
|
|
152
|
+
const channels = textChannels.length;
|
|
154
153
|
this.client.emit(Events.CACHE_SWEEP, `Swept ${messages} messages in ${channels} text-based channels.`);
|
|
155
154
|
return messages;
|
|
156
155
|
}
|
|
@@ -173,19 +172,19 @@ class Sweepers {
|
|
|
173
172
|
if (typeof filter !== 'function') {
|
|
174
173
|
throw new TypeError('INVALID_TYPE', 'filter', 'function');
|
|
175
174
|
}
|
|
176
|
-
|
|
175
|
+
const textChannels = this._getTextChannels();
|
|
177
176
|
let messages = 0;
|
|
178
177
|
let reactions = 0;
|
|
179
178
|
|
|
180
|
-
for (const channel of
|
|
181
|
-
if (!channel.isText()) continue;
|
|
182
|
-
channels++;
|
|
183
|
-
|
|
179
|
+
for (const channel of textChannels) {
|
|
184
180
|
for (const message of channel.messages.cache.values()) {
|
|
181
|
+
const reactionCache = message.reactions?.cache;
|
|
182
|
+
if (!reactionCache?.size) continue;
|
|
185
183
|
messages++;
|
|
186
|
-
reactions +=
|
|
184
|
+
reactions += reactionCache.sweep(filter);
|
|
187
185
|
}
|
|
188
186
|
}
|
|
187
|
+
const channels = textChannels.length;
|
|
189
188
|
this.client.emit(
|
|
190
189
|
Events.CACHE_SWEEP,
|
|
191
190
|
`Swept ${reactions} reactions on ${messages} messages in ${channels} text-based channels.`,
|
|
@@ -390,6 +389,34 @@ class Sweepers {
|
|
|
390
389
|
* @private
|
|
391
390
|
*/
|
|
392
391
|
|
|
392
|
+
_getValidatedOptions(key) {
|
|
393
|
+
if (this._validatedOptions.has(key)) {
|
|
394
|
+
return this._validatedOptions.get(key);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this._validateProperties(key);
|
|
398
|
+
const clonedOptions = { ...this.options[key] };
|
|
399
|
+
|
|
400
|
+
if (!('filter' in clonedOptions)) {
|
|
401
|
+
switch (key) {
|
|
402
|
+
case 'invites':
|
|
403
|
+
clonedOptions.filter = this.constructor.expiredInviteSweepFilter(clonedOptions.lifetime);
|
|
404
|
+
break;
|
|
405
|
+
case 'messages':
|
|
406
|
+
clonedOptions.filter = this.constructor.outdatedMessageSweepFilter(clonedOptions.lifetime);
|
|
407
|
+
break;
|
|
408
|
+
case 'threads':
|
|
409
|
+
clonedOptions.filter = this.constructor.archivedThreadSweepFilter(clonedOptions.lifetime);
|
|
410
|
+
break;
|
|
411
|
+
default:
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
this._validatedOptions.set(key, clonedOptions);
|
|
417
|
+
return clonedOptions;
|
|
418
|
+
}
|
|
419
|
+
|
|
393
420
|
/**
|
|
394
421
|
* Sweep a direct sub property of all guilds
|
|
395
422
|
* @param {string} key The name of the property
|
package/typings/enums.d.ts
CHANGED
|
@@ -436,7 +436,7 @@ export const enum ApplicationType {
|
|
|
436
436
|
CREATOR_MONETIZATION = 4,
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
export enum ClientEvents {
|
|
439
|
+
export const enum ClientEvents {
|
|
440
440
|
RateLimit = 'rateLimit',
|
|
441
441
|
InvalidRequestWarning = 'invalidRequestWarning',
|
|
442
442
|
ApiResponse = 'apiResponse',
|