seyfert 4.4.1 → 4.6.0
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 +7 -1
- package/lib/api/Routes/guilds.d.ts +12 -1
- package/lib/api/Routes/index.d.ts +3 -1
- package/lib/api/Routes/invites.d.ts +1 -1
- package/lib/api/Routes/oauth2.d.ts +11 -0
- package/lib/api/Routes/oauth2.js +2 -0
- package/lib/api/Routes/skus.d.ts +11 -4
- package/lib/api/Routes/users.d.ts +2 -1
- package/lib/api/Routes/voice.d.ts +4 -0
- package/lib/api/Routes/webhooks.d.ts +1 -1
- package/lib/api/api.d.ts +15 -5
- package/lib/api/api.js +243 -114
- package/lib/api/bucket.js +13 -7
- package/lib/api/shared.d.ts +3 -1
- package/lib/api/utils/constants.d.ts +1 -1
- package/lib/api/utils/constants.js +1 -1
- package/lib/builders/Attachment.d.ts +6 -1
- package/lib/builders/Attachment.js +13 -9
- package/lib/builders/CheckboxGroup.js +1 -1
- package/lib/builders/FileUpload.d.ts +8 -1
- package/lib/builders/FileUpload.js +9 -0
- package/lib/builders/RadioGroup.js +1 -1
- package/lib/client/base.d.ts +3 -3
- package/lib/client/client.d.ts +1 -1
- package/lib/client/client.js +5 -4
- package/lib/client/collectors.d.ts +10 -8
- package/lib/client/transformers.d.ts +5 -0
- package/lib/client/workerclient.d.ts +8 -1
- package/lib/client/workerclient.js +34 -14
- package/lib/commands/applications/chat.d.ts +6 -4
- package/lib/commands/applications/options.d.ts +6 -4
- package/lib/commands/applications/shared.d.ts +2 -2
- package/lib/commands/handle.d.ts +7 -3
- package/lib/commands/handle.js +36 -11
- package/lib/commands/handler.js +11 -0
- package/lib/commands/optionresolver.d.ts +6 -7
- package/lib/common/it/error.d.ts +13 -0
- package/lib/common/it/error.js +15 -1
- package/lib/common/it/logger.js +12 -9
- package/lib/common/shorters/application.d.ts +1 -1
- package/lib/common/shorters/application.js +1 -1
- package/lib/common/shorters/bans.js +1 -1
- package/lib/common/shorters/guilds.js +5 -1
- package/lib/common/shorters/interaction.js +7 -4
- package/lib/common/shorters/invites.d.ts +5 -0
- package/lib/common/shorters/members.js +2 -1
- package/lib/common/shorters/messages.js +2 -2
- package/lib/common/shorters/threads.js +1 -1
- package/lib/common/shorters/webhook.js +3 -3
- package/lib/common/types/util.d.ts +7 -0
- package/lib/components/componentcommand.js +2 -2
- package/lib/components/customId.d.ts +3 -0
- package/lib/components/customId.js +11 -0
- package/lib/components/handler.js +2 -3
- package/lib/components/modalcommand.js +2 -2
- package/lib/events/hooks/invite.d.ts +1 -0
- package/lib/events/hooks/presence.d.ts +1 -0
- package/lib/events/hooks/thread.d.ts +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -1
- package/lib/structures/AnonymousGuild.d.ts +1 -1
- package/lib/structures/AnonymousGuild.js +1 -1
- package/lib/structures/Guild.d.ts +3 -0
- package/lib/structures/GuildMember.d.ts +2 -2
- package/lib/structures/GuildMember.js +22 -4
- package/lib/structures/GuildPreview.d.ts +1 -1
- package/lib/structures/GuildPreview.js +1 -1
- package/lib/structures/GuildRole.d.ts +13 -0
- package/lib/structures/GuildRole.js +21 -0
- package/lib/structures/GuildTemplate.js +1 -1
- package/lib/structures/Interaction.d.ts +7 -7
- package/lib/structures/Interaction.js +35 -21
- package/lib/structures/Message.d.ts +2 -1
- package/lib/structures/channels.d.ts +29 -16
- package/lib/structures/channels.js +59 -19
- package/lib/structures/extra/BaseGuild.d.ts +4 -4
- package/lib/structures/extra/BaseGuild.js +4 -4
- package/lib/types/gateway.d.ts +243 -239
- package/lib/types/index.d.ts +2 -2
- package/lib/types/payloads/_interactions/_applicationCommands/_chatInput/attachment.d.ts +5 -2
- package/lib/types/payloads/_interactions/_applicationCommands/_chatInput/shared.d.ts +2 -2
- package/lib/types/payloads/_interactions/_applicationCommands/_chatInput/shared.js +1 -1
- package/lib/types/payloads/_interactions/_applicationCommands/chatInput.d.ts +7 -7
- package/lib/types/payloads/_interactions/_applicationCommands/contextMenu.d.ts +13 -13
- package/lib/types/payloads/_interactions/_applicationCommands/permissions.d.ts +4 -4
- package/lib/types/payloads/_interactions/_applicationCommands/permissions.js +2 -2
- package/lib/types/payloads/_interactions/applicationCommands.d.ts +10 -10
- package/lib/types/payloads/_interactions/applicationCommands.js +4 -4
- package/lib/types/payloads/_interactions/autocomplete.d.ts +2 -2
- package/lib/types/payloads/_interactions/base.d.ts +13 -7
- package/lib/types/payloads/_interactions/modalSubmit.d.ts +4 -4
- package/lib/types/payloads/_interactions/responses.d.ts +10 -10
- package/lib/types/payloads/_interactions/responses.js +2 -2
- package/lib/types/payloads/application.d.ts +21 -15
- package/lib/types/payloads/application.js +7 -7
- package/lib/types/payloads/auditLog.d.ts +22 -17
- package/lib/types/payloads/auditLog.js +7 -2
- package/lib/types/payloads/autoModeration.d.ts +11 -11
- package/lib/types/payloads/autoModeration.js +5 -5
- package/lib/types/payloads/channel.d.ts +87 -78
- package/lib/types/payloads/channel.js +16 -11
- package/lib/types/payloads/components.d.ts +41 -31
- package/lib/types/payloads/components.js +9 -5
- package/lib/types/payloads/emoji.d.ts +2 -2
- package/lib/types/payloads/emoji.js +1 -1
- package/lib/types/payloads/gateway.d.ts +33 -29
- package/lib/types/payloads/gateway.js +5 -5
- package/lib/types/payloads/guild.d.ts +68 -68
- package/lib/types/payloads/guild.js +14 -14
- package/lib/types/payloads/guildScheduledEvent.d.ts +11 -11
- package/lib/types/payloads/guildScheduledEvent.js +3 -3
- package/lib/types/payloads/index.d.ts +2 -2
- package/lib/types/payloads/interactions.d.ts +3 -3
- package/lib/types/payloads/invite.d.ts +11 -11
- package/lib/types/payloads/invite.js +3 -3
- package/lib/types/payloads/monetization.d.ts +4 -4
- package/lib/types/payloads/monetization.js +2 -2
- package/lib/types/payloads/oauth2.d.ts +21 -17
- package/lib/types/payloads/oauth2.js +21 -17
- package/lib/types/payloads/permissions.d.ts +5 -5
- package/lib/types/payloads/permissions.js +2 -2
- package/lib/types/payloads/poll.d.ts +7 -7
- package/lib/types/payloads/poll.js +2 -2
- package/lib/types/payloads/soundboard.d.ts +2 -2
- package/lib/types/payloads/soundboard.js +1 -1
- package/lib/types/payloads/stageInstance.d.ts +5 -5
- package/lib/types/payloads/stageInstance.js +1 -1
- package/lib/types/payloads/sticker.d.ts +8 -8
- package/lib/types/payloads/sticker.js +3 -3
- package/lib/types/payloads/teams.d.ts +8 -8
- package/lib/types/payloads/teams.js +3 -3
- package/lib/types/payloads/template.d.ts +3 -3
- package/lib/types/payloads/template.js +1 -1
- package/lib/types/payloads/user.d.ts +24 -22
- package/lib/types/payloads/user.js +7 -5
- package/lib/types/payloads/voice.d.ts +4 -4
- package/lib/types/payloads/voice.js +1 -1
- package/lib/types/payloads/webhokEvents.d.ts +5 -5
- package/lib/types/payloads/webhokEvents.js +2 -2
- package/lib/types/payloads/webhook.d.ts +4 -4
- package/lib/types/payloads/webhook.js +1 -1
- package/lib/types/rest/application.d.ts +14 -14
- package/lib/types/rest/auditLog.d.ts +1 -1
- package/lib/types/rest/autoModeration.d.ts +7 -7
- package/lib/types/rest/channel.d.ts +79 -74
- package/lib/types/rest/channel.js +1 -1
- package/lib/types/rest/emoji.d.ts +8 -8
- package/lib/types/rest/gateway.d.ts +2 -2
- package/lib/types/rest/guild.d.ts +98 -98
- package/lib/types/rest/guild.js +5 -5
- package/lib/types/rest/guildScheduledEvent.d.ts +11 -11
- package/lib/types/rest/index.d.ts +34 -3
- package/lib/types/rest/index.js +33 -2
- package/lib/types/rest/interactions.d.ts +48 -48
- package/lib/types/rest/invite.d.ts +5 -5
- package/lib/types/rest/monetization.d.ts +9 -9
- package/lib/types/rest/monetization.js +1 -1
- package/lib/types/rest/oauth2.d.ts +22 -18
- package/lib/types/rest/poll.d.ts +4 -4
- package/lib/types/rest/soundboard.d.ts +8 -8
- package/lib/types/rest/stageInstance.d.ts +6 -6
- package/lib/types/rest/sticker.d.ts +10 -10
- package/lib/types/rest/template.d.ts +11 -11
- package/lib/types/rest/user.d.ts +18 -14
- package/lib/types/rest/voice.d.ts +5 -5
- package/lib/types/rest/webhook.d.ts +38 -38
- package/lib/types/utils/index.d.ts +19 -19
- package/lib/types/utils/index.js +19 -19
- package/lib/websocket/SharedTypes.d.ts +2 -2
- package/lib/websocket/constants/index.js +2 -2
- package/lib/websocket/discord/heartbeater.d.ts +1 -0
- package/lib/websocket/discord/heartbeater.js +8 -0
- package/lib/websocket/discord/shard.d.ts +19 -10
- package/lib/websocket/discord/shard.js +255 -122
- package/lib/websocket/discord/sharder.d.ts +2 -2
- package/lib/websocket/discord/sharder.js +2 -2
- package/lib/websocket/discord/shared.d.ts +7 -2
- package/lib/websocket/discord/workermanager.d.ts +3 -1
- package/lib/websocket/discord/workermanager.js +31 -24
- package/package.json +3 -3
package/lib/api/api.js
CHANGED
|
@@ -8,11 +8,32 @@ const bucket_1 = require("./bucket");
|
|
|
8
8
|
const Router_1 = require("./Router");
|
|
9
9
|
const shared_1 = require("./shared");
|
|
10
10
|
const utils_2 = require("./utils/utils");
|
|
11
|
+
// JavaScript timers use a signed 32-bit millisecond delay.
|
|
12
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
13
|
+
const MAX_BUCKET_RESET_DELAY_MS = MAX_TIMER_DELAY_MS - 1;
|
|
14
|
+
const MAX_SMART_BUCKET_RESET_AFTER_MS = Math.floor(MAX_TIMER_DELAY_MS / 1.5);
|
|
15
|
+
function parseRateLimitCount(raw, minimum) {
|
|
16
|
+
if (raw === null)
|
|
17
|
+
return;
|
|
18
|
+
if (!/^\d+$/.test(raw))
|
|
19
|
+
return null;
|
|
20
|
+
const value = Number(raw);
|
|
21
|
+
return Number.isSafeInteger(value) && value >= minimum ? value : null;
|
|
22
|
+
}
|
|
23
|
+
function parseRateLimitDelay(value) {
|
|
24
|
+
if (value === null || (typeof value === 'string' && value.trim() === ''))
|
|
25
|
+
return;
|
|
26
|
+
const milliseconds = Number(value) * 1000;
|
|
27
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 ? milliseconds : undefined;
|
|
28
|
+
}
|
|
11
29
|
class ApiHandler {
|
|
12
30
|
options;
|
|
13
31
|
globalBlock = false;
|
|
14
32
|
ratelimits = new Map();
|
|
15
33
|
readyQueue = [];
|
|
34
|
+
bucketAliases = new Map();
|
|
35
|
+
globalBlocks = new Map();
|
|
36
|
+
globalTimers = new Map();
|
|
16
37
|
cdn = Router_1.CDNRouter.createProxy();
|
|
17
38
|
workerPromises;
|
|
18
39
|
onRatelimit;
|
|
@@ -70,6 +91,14 @@ class ApiHandler {
|
|
|
70
91
|
return (this._proxy_ ??= new Router_1.Router(this).createProxy());
|
|
71
92
|
}
|
|
72
93
|
globalUnblock() {
|
|
94
|
+
for (const timer of this.globalTimers.values())
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
this.globalTimers.clear();
|
|
97
|
+
for (const queue of this.globalBlocks.values()) {
|
|
98
|
+
for (const callback of queue)
|
|
99
|
+
callback();
|
|
100
|
+
}
|
|
101
|
+
this.globalBlocks.clear();
|
|
73
102
|
this.globalBlock = false;
|
|
74
103
|
let cb;
|
|
75
104
|
while ((cb = this.readyQueue.shift())) {
|
|
@@ -107,7 +136,11 @@ class ApiHandler {
|
|
|
107
136
|
this.debugger?.warn('onFailRequest callback error', callbackError);
|
|
108
137
|
}
|
|
109
138
|
}
|
|
110
|
-
async request(method, url,
|
|
139
|
+
async request(method, url, request = {}) {
|
|
140
|
+
const requestOptions = { ...request };
|
|
141
|
+
const { auth = true } = requestOptions;
|
|
142
|
+
let attempts = requestOptions._50xRetries ?? 0;
|
|
143
|
+
delete requestOptions._50xRetries;
|
|
111
144
|
const originTrace = {};
|
|
112
145
|
Error.captureStackTrace(originTrace, this.request);
|
|
113
146
|
if (this.options.workerProxy) {
|
|
@@ -118,11 +151,12 @@ class ApiHandler {
|
|
|
118
151
|
type: 'WORKER_API_REQUEST',
|
|
119
152
|
workerId: this.workerData.workerId,
|
|
120
153
|
nonce,
|
|
121
|
-
requestOptions: { auth, ...
|
|
154
|
+
requestOptions: { auth, ...requestOptions },
|
|
122
155
|
});
|
|
123
156
|
}
|
|
124
|
-
const route =
|
|
125
|
-
|
|
157
|
+
const route = requestOptions.route || this.routefy(url, method);
|
|
158
|
+
const provisionalRoute = requestOptions.route || `${method}:${route}`;
|
|
159
|
+
let bucketRoute = this.bucketAliases.get(provisionalRoute) ?? provisionalRoute;
|
|
126
160
|
const callback = async (next, resolve, reject) => {
|
|
127
161
|
const headers = {
|
|
128
162
|
'User-Agent': this.options.userAgent,
|
|
@@ -130,7 +164,7 @@ class ApiHandler {
|
|
|
130
164
|
const { data, finalUrl } = this.parseRequest({
|
|
131
165
|
url,
|
|
132
166
|
headers,
|
|
133
|
-
request: { ...
|
|
167
|
+
request: { ...requestOptions, auth },
|
|
134
168
|
});
|
|
135
169
|
let response;
|
|
136
170
|
try {
|
|
@@ -152,77 +186,105 @@ class ApiHandler {
|
|
|
152
186
|
}
|
|
153
187
|
const now = Date.now();
|
|
154
188
|
const headerNow = Date.parse(response.headers.get('date') ?? '');
|
|
155
|
-
this.
|
|
156
|
-
this.
|
|
157
|
-
|
|
189
|
+
bucketRoute = this.learnBucket(provisionalRoute, bucketRoute, url, response, requestOptions.route !== undefined);
|
|
190
|
+
this.setRatelimitsBucket(bucketRoute, response);
|
|
191
|
+
this.setResetBucket(bucketRoute, response, now, headerNow, route);
|
|
192
|
+
this.ratelimits.get(bucketRoute).process();
|
|
193
|
+
const needsObserverBody = (response.status < 300 && this.onSuccessRequest !== undefined) ||
|
|
194
|
+
(response.status === 429 && this.onRatelimit !== undefined);
|
|
195
|
+
const observerResponse = needsObserverBody ? response.clone() : response;
|
|
196
|
+
let result;
|
|
197
|
+
try {
|
|
198
|
+
result = await decodeResponse(response);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
await this.notifyFailRequest(method, finalUrl, error, response.status);
|
|
202
|
+
next();
|
|
203
|
+
reject(error);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
158
206
|
if (response.status >= 300) {
|
|
207
|
+
const errorResult = normalizeErrorResult(result);
|
|
159
208
|
if (response.status === 429) {
|
|
160
|
-
const result429 = await this.handle429(
|
|
209
|
+
const result429 = await this.handle429(bucketRoute, method, url, { ...requestOptions, auth }, observerResponse, typeof errorResult === 'string' ? errorResult : JSON.stringify(errorResult), next, reject, now);
|
|
161
210
|
if (result429 !== false)
|
|
162
211
|
return resolve(result429);
|
|
163
|
-
await this.notifyFailRequest(method, finalUrl,
|
|
164
|
-
return this.clearResetInterval(
|
|
212
|
+
await this.notifyFailRequest(method, finalUrl, errorResult, response.status);
|
|
213
|
+
return this.clearResetInterval(bucketRoute);
|
|
165
214
|
}
|
|
166
215
|
if ([502, 503].includes(response.status) && ++attempts < 4) {
|
|
167
|
-
this.clearResetInterval(
|
|
168
|
-
return this.handle50X(method, url,
|
|
216
|
+
this.clearResetInterval(bucketRoute);
|
|
217
|
+
return this.handle50X(method, url, requestOptions, attempts, next, resolve, reject);
|
|
169
218
|
}
|
|
170
|
-
this.clearResetInterval(
|
|
219
|
+
this.clearResetInterval(bucketRoute);
|
|
171
220
|
next();
|
|
172
|
-
|
|
173
|
-
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
174
|
-
try {
|
|
175
|
-
result = JSON.parse(result);
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
this.debugger?.warn('SeyfertError parsing result error (', result, ')', err);
|
|
179
|
-
await this.notifyFailRequest(method, finalUrl, err, response.status);
|
|
180
|
-
reject(err);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
const parsedError = this.parseError(method, route, response, result, originTrace);
|
|
221
|
+
const parsedError = this.parseError(method, route, response, errorResult, originTrace);
|
|
186
222
|
this.debugger?.warn(parsedError.message);
|
|
187
223
|
await this.notifyFailRequest(method, finalUrl, parsedError, response.status);
|
|
188
224
|
reject(parsedError);
|
|
189
225
|
return;
|
|
190
226
|
}
|
|
191
|
-
|
|
192
|
-
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
193
|
-
try {
|
|
194
|
-
result = JSON.parse(result);
|
|
195
|
-
}
|
|
196
|
-
catch (err) {
|
|
197
|
-
this.debugger?.warn('Failed parsing result (', result, ')', err);
|
|
198
|
-
await this.notifyFailRequest(method, finalUrl, err, response.status);
|
|
199
|
-
next();
|
|
200
|
-
reject(err);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
await this.notifySuccessRequest(method, finalUrl, response);
|
|
227
|
+
await this.notifySuccessRequest(method, finalUrl, observerResponse);
|
|
206
228
|
next();
|
|
207
|
-
return resolve(result
|
|
229
|
+
return resolve(result);
|
|
208
230
|
};
|
|
209
231
|
return new Promise((resolve, reject) => {
|
|
210
|
-
|
|
211
|
-
this.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
const dispatch = () => {
|
|
233
|
+
bucketRoute = this.bucketAliases.get(provisionalRoute) ?? bucketRoute;
|
|
234
|
+
if (!this.ratelimits.has(bucketRoute)) {
|
|
235
|
+
this.ratelimits.set(bucketRoute, new bucket_1.Bucket(1));
|
|
236
|
+
}
|
|
237
|
+
this.ratelimits.get(bucketRoute).push({ next: callback, resolve, reject }, requestOptions.unshift);
|
|
238
|
+
};
|
|
239
|
+
const globalQueue = this.globalBlocks.get(this.globalRateLimitKey({ ...requestOptions, auth }));
|
|
240
|
+
if (globalQueue && !url.startsWith('/interactions/')) {
|
|
241
|
+
globalQueue.push(dispatch);
|
|
217
242
|
}
|
|
218
243
|
else {
|
|
219
|
-
|
|
220
|
-
this.ratelimits.set(route, new bucket_1.Bucket(1));
|
|
221
|
-
}
|
|
222
|
-
this.ratelimits.get(route).push({ next: callback, resolve, reject }, request.unshift);
|
|
244
|
+
dispatch();
|
|
223
245
|
}
|
|
224
246
|
});
|
|
225
247
|
}
|
|
248
|
+
learnBucket(provisionalRoute, currentRoute, url, response, hasRouteOverride) {
|
|
249
|
+
const hash = response.headers.get('x-ratelimit-bucket');
|
|
250
|
+
if (!hash || hasRouteOverride)
|
|
251
|
+
return currentRoute;
|
|
252
|
+
const learnedRoute = `${hash}:${majorParameter(url)}`;
|
|
253
|
+
this.bucketAliases.set(provisionalRoute, learnedRoute);
|
|
254
|
+
const currentBucket = this.ratelimits.get(currentRoute);
|
|
255
|
+
const learnedBucket = this.ratelimits.get(learnedRoute);
|
|
256
|
+
if (!learnedBucket) {
|
|
257
|
+
this.ratelimits.set(learnedRoute, currentBucket);
|
|
258
|
+
return learnedRoute;
|
|
259
|
+
}
|
|
260
|
+
if (learnedBucket !== currentBucket && currentBucket.queue.length) {
|
|
261
|
+
learnedBucket.queue.push(...currentBucket.queue.splice(0));
|
|
262
|
+
}
|
|
263
|
+
return learnedRoute;
|
|
264
|
+
}
|
|
265
|
+
globalRateLimitKey(request) {
|
|
266
|
+
if (request.auth === false)
|
|
267
|
+
return 'unauthenticated';
|
|
268
|
+
return `${this.options.type}:${request.token || this.options.token}`;
|
|
269
|
+
}
|
|
270
|
+
blockGlobal(request, retryAfter) {
|
|
271
|
+
const key = this.globalRateLimitKey(request);
|
|
272
|
+
if (!this.globalBlocks.has(key))
|
|
273
|
+
this.globalBlocks.set(key, []);
|
|
274
|
+
const previousTimer = this.globalTimers.get(key);
|
|
275
|
+
if (previousTimer)
|
|
276
|
+
clearTimeout(previousTimer);
|
|
277
|
+
this.globalBlock = true;
|
|
278
|
+
this.globalTimers.set(key, setTimeout(() => this.unblockGlobal(key), retryAfter || 1));
|
|
279
|
+
}
|
|
280
|
+
unblockGlobal(key) {
|
|
281
|
+
this.globalTimers.delete(key);
|
|
282
|
+
const queue = this.globalBlocks.get(key) ?? [];
|
|
283
|
+
this.globalBlocks.delete(key);
|
|
284
|
+
this.globalBlock = this.globalBlocks.size > 0;
|
|
285
|
+
for (const callback of queue)
|
|
286
|
+
callback();
|
|
287
|
+
}
|
|
226
288
|
parseError(method, route, response, result, originTrace) {
|
|
227
289
|
let errMessage = '';
|
|
228
290
|
let code;
|
|
@@ -279,29 +341,50 @@ class ApiHandler {
|
|
|
279
341
|
}
|
|
280
342
|
return errors;
|
|
281
343
|
}
|
|
282
|
-
async handle50X(method, url, request, next) {
|
|
344
|
+
async handle50X(method, url, request, attempts, next = () => { }, resolve, reject) {
|
|
345
|
+
const retryAttempt = typeof attempts === 'number' ? attempts : 0;
|
|
346
|
+
const callback = typeof attempts === 'function' ? attempts : next;
|
|
347
|
+
const requestOptions = {
|
|
348
|
+
...request,
|
|
349
|
+
unshift: true,
|
|
350
|
+
...(retryAttempt > 0 ? { _50xRetries: retryAttempt } : {}),
|
|
351
|
+
};
|
|
283
352
|
const wait = Math.floor(Math.random() * 1900 + 100);
|
|
284
353
|
this.debugger?.warn(`Handling a 50X status, retrying in ${wait}ms`);
|
|
285
|
-
|
|
354
|
+
callback();
|
|
286
355
|
await (0, common_1.delay)(wait);
|
|
287
|
-
return this.request(method, url,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
356
|
+
return this.request(method, url, requestOptions)
|
|
357
|
+
.then(value => {
|
|
358
|
+
resolve?.(value);
|
|
359
|
+
return value;
|
|
360
|
+
})
|
|
361
|
+
.catch(error => {
|
|
362
|
+
reject?.(error);
|
|
293
363
|
});
|
|
294
364
|
}
|
|
295
365
|
async handle429(route, method, url, request, response, result, next, reject, now) {
|
|
296
|
-
await this.onRatelimit?.(response, request);
|
|
297
366
|
const bucket = this.ratelimits.get(route);
|
|
298
367
|
let retryAfter;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
368
|
+
let data;
|
|
369
|
+
try {
|
|
370
|
+
const parsed = JSON.parse(result);
|
|
371
|
+
if (isPlainObject(parsed))
|
|
372
|
+
data = parsed;
|
|
373
|
+
}
|
|
374
|
+
catch { }
|
|
375
|
+
if (typeof data?.retry_after === 'number') {
|
|
376
|
+
const value = parseRateLimitDelay(data.retry_after);
|
|
377
|
+
if (value !== undefined && value <= MAX_TIMER_DELAY_MS)
|
|
378
|
+
retryAfter = Math.ceil(value);
|
|
379
|
+
}
|
|
380
|
+
for (const header of ['retry-after', 'x-ratelimit-reset-after']) {
|
|
381
|
+
if (retryAfter !== undefined)
|
|
382
|
+
break;
|
|
383
|
+
const value = parseRateLimitDelay(response.headers.get(header));
|
|
384
|
+
if (value !== undefined && value <= MAX_TIMER_DELAY_MS)
|
|
385
|
+
retryAfter = value;
|
|
386
|
+
}
|
|
387
|
+
if (retryAfter === undefined) {
|
|
305
388
|
this.debugger?.warn(`${route} Could not extract retry_after from 429 response. ${result}`);
|
|
306
389
|
next();
|
|
307
390
|
reject(new common_1.SeyfertError('INVALID_RETRY_AFTER', {
|
|
@@ -317,71 +400,75 @@ class ApiHandler {
|
|
|
317
400
|
}));
|
|
318
401
|
return false;
|
|
319
402
|
}
|
|
403
|
+
if (data?.global === true ||
|
|
404
|
+
response.headers.has('x-ratelimit-global') ||
|
|
405
|
+
response.headers.get('x-ratelimit-scope') === 'global') {
|
|
406
|
+
this.blockGlobal(request, retryAfter);
|
|
407
|
+
}
|
|
408
|
+
await this.onRatelimit?.(response, request);
|
|
320
409
|
if (this.debugger) {
|
|
321
410
|
const content = `${JSON.stringify(request)} `;
|
|
322
411
|
this.debugger.info(`${response.headers.get('x-ratelimit-global') ? 'Global' : 'Unexpected'} 429: ${result.slice(0, 256)}\n${content} ${now} ${route} ${response.status}: ${bucket.remaining}/${bucket.limit} left | Reset ${retryAfter} (${bucket.reset - now}ms left) | Scope ${response.headers.get('x-ratelimit-scope')}`);
|
|
323
412
|
}
|
|
324
|
-
if (retryAfter)
|
|
413
|
+
if (retryAfter)
|
|
325
414
|
await (0, common_1.delay)(retryAfter);
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
body: request.body,
|
|
329
|
-
auth: request.auth,
|
|
330
|
-
reason: request.reason,
|
|
331
|
-
route: request.route,
|
|
332
|
-
unshift: true,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
next();
|
|
336
|
-
return this.request(method, url, {
|
|
337
|
-
body: request.body,
|
|
338
|
-
auth: request.auth,
|
|
339
|
-
reason: request.reason,
|
|
340
|
-
route: request.route,
|
|
415
|
+
const retry = this.request(method, url, {
|
|
416
|
+
...request,
|
|
341
417
|
unshift: true,
|
|
342
418
|
});
|
|
419
|
+
next();
|
|
420
|
+
return retry.catch(reject);
|
|
343
421
|
}
|
|
344
422
|
clearResetInterval(route) {
|
|
345
423
|
clearInterval(this.ratelimits.get(route).processingResetAfter);
|
|
346
424
|
this.ratelimits.get(route).processingResetAfter = undefined;
|
|
347
425
|
this.ratelimits.get(route).resetAfter = 0;
|
|
348
426
|
}
|
|
349
|
-
setResetBucket(route, resp, now, headerNow) {
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
this.ratelimits.get(route).reset = (retryAfter || 1) + now;
|
|
358
|
-
}
|
|
427
|
+
setResetBucket(route, resp, now, headerNow, normalizedRoute = route) {
|
|
428
|
+
const bucket = this.ratelimits.get(route);
|
|
429
|
+
const retryAfterHeader = resp.headers.get('x-ratelimit-reset-after') ?? resp.headers.get('retry-after');
|
|
430
|
+
const retryAfter = parseRateLimitDelay(retryAfterHeader);
|
|
431
|
+
if (retryAfter !== undefined) {
|
|
432
|
+
bucket.reset = Math.min(retryAfter || 1, MAX_BUCKET_RESET_DELAY_MS) + now;
|
|
359
433
|
}
|
|
360
|
-
else
|
|
361
|
-
|
|
362
|
-
|
|
434
|
+
else {
|
|
435
|
+
const resetHeader = resp.headers.get('x-ratelimit-reset');
|
|
436
|
+
const parsedReset = parseRateLimitDelay(resetHeader);
|
|
437
|
+
if (parsedReset === undefined) {
|
|
438
|
+
if (retryAfterHeader === null && resetHeader === null)
|
|
439
|
+
bucket.reset = now;
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
let resetTime = parsedReset;
|
|
443
|
+
if (normalizedRoute.endsWith('/reactions/:id') && resetTime - headerNow === 1000) {
|
|
363
444
|
resetTime = now + 250;
|
|
364
445
|
}
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
this.ratelimits.get(route).reset = now;
|
|
446
|
+
bucket.reset = Math.min(Math.max(resetTime, now), now + MAX_BUCKET_RESET_DELAY_MS);
|
|
369
447
|
}
|
|
370
448
|
}
|
|
371
449
|
setRatelimitsBucket(route, resp) {
|
|
372
|
-
|
|
373
|
-
|
|
450
|
+
const bucket = this.ratelimits.get(route);
|
|
451
|
+
const limit = parseRateLimitCount(resp.headers.get('x-ratelimit-limit'), 1);
|
|
452
|
+
const remaining = parseRateLimitCount(resp.headers.get('x-ratelimit-remaining'), 0);
|
|
453
|
+
if (limit !== null && limit !== undefined) {
|
|
454
|
+
bucket.limit = limit;
|
|
455
|
+
bucket.remaining = Math.min(bucket.remaining, limit);
|
|
456
|
+
}
|
|
457
|
+
if (remaining === undefined) {
|
|
458
|
+
bucket.remaining = Math.min(1, bucket.limit);
|
|
459
|
+
}
|
|
460
|
+
else if (remaining !== null) {
|
|
461
|
+
bucket.remaining = Math.min(remaining, bucket.limit);
|
|
374
462
|
}
|
|
375
|
-
this.ratelimits.get(route).remaining =
|
|
376
|
-
resp.headers.get('x-ratelimit-remaining') === undefined ? 1 : +resp.headers.get('x-ratelimit-remaining');
|
|
377
463
|
if (this.options.smartBucket) {
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
464
|
+
if (typeof limit === 'number' && typeof remaining === 'number' && !bucket.resetAfter && limit === remaining + 1) {
|
|
465
|
+
const resetAfter = parseRateLimitDelay(resp.headers.get('x-ratelimit-reset-after'));
|
|
466
|
+
if (resetAfter !== undefined && resetAfter <= MAX_SMART_BUCKET_RESET_AFTER_MS) {
|
|
467
|
+
bucket.resetAfter = resetAfter;
|
|
468
|
+
}
|
|
382
469
|
}
|
|
383
|
-
if (
|
|
384
|
-
|
|
470
|
+
if (bucket.resetAfter && !bucket.remaining) {
|
|
471
|
+
bucket.triggerResetAfter();
|
|
385
472
|
}
|
|
386
473
|
}
|
|
387
474
|
}
|
|
@@ -394,8 +481,12 @@ class ApiHandler {
|
|
|
394
481
|
if (options.request.query) {
|
|
395
482
|
const params = new URLSearchParams();
|
|
396
483
|
for (const [key, value] of Object.entries(options.request.query)) {
|
|
484
|
+
if (value === null || value === undefined)
|
|
485
|
+
continue;
|
|
397
486
|
if (Array.isArray(value)) {
|
|
398
487
|
for (const item of value) {
|
|
488
|
+
if (item === null || item === undefined)
|
|
489
|
+
continue;
|
|
399
490
|
params.append(key, String(item));
|
|
400
491
|
}
|
|
401
492
|
}
|
|
@@ -403,7 +494,9 @@ class ApiHandler {
|
|
|
403
494
|
params.append(key, String(value));
|
|
404
495
|
}
|
|
405
496
|
}
|
|
406
|
-
|
|
497
|
+
const query = params.toString();
|
|
498
|
+
if (query)
|
|
499
|
+
finalUrl += `${finalUrl.includes('?') ? '&' : '?'}${query}`;
|
|
407
500
|
}
|
|
408
501
|
if (options.request.files?.length || options.request.appendToFormData) {
|
|
409
502
|
const formData = new FormData();
|
|
@@ -471,3 +564,39 @@ class ApiHandler {
|
|
|
471
564
|
}
|
|
472
565
|
}
|
|
473
566
|
exports.ApiHandler = ApiHandler;
|
|
567
|
+
function isPlainObject(value) {
|
|
568
|
+
if (typeof value !== 'object' || value === null)
|
|
569
|
+
return false;
|
|
570
|
+
const prototype = Object.getPrototypeOf(value);
|
|
571
|
+
return prototype === Object.prototype || prototype === null;
|
|
572
|
+
}
|
|
573
|
+
function normalizeErrorResult(result) {
|
|
574
|
+
if (typeof result === 'string' || isPlainObject(result))
|
|
575
|
+
return result;
|
|
576
|
+
return JSON.stringify(result) ?? String(result);
|
|
577
|
+
}
|
|
578
|
+
async function decodeResponse(response) {
|
|
579
|
+
const contentType = response.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase();
|
|
580
|
+
if (response.status < 300 && contentType && !contentType.startsWith('text/') && !isJsonContentType(contentType)) {
|
|
581
|
+
return response.arrayBuffer();
|
|
582
|
+
}
|
|
583
|
+
const text = await response.text();
|
|
584
|
+
if (!text.length)
|
|
585
|
+
return undefined;
|
|
586
|
+
return isJsonContentType(contentType) ? JSON.parse(text) : text;
|
|
587
|
+
}
|
|
588
|
+
function isJsonContentType(contentType) {
|
|
589
|
+
return contentType === 'application/json' || contentType?.endsWith('+json') === true;
|
|
590
|
+
}
|
|
591
|
+
function majorParameter(url) {
|
|
592
|
+
const channel = url.match(/^\/channels\/([^/]+)/)?.[1];
|
|
593
|
+
if (channel)
|
|
594
|
+
return `channels:${channel}`;
|
|
595
|
+
const guild = url.match(/^\/guilds\/([^/]+)/)?.[1];
|
|
596
|
+
if (guild)
|
|
597
|
+
return `guilds:${guild}`;
|
|
598
|
+
const webhook = url.match(/^\/webhooks\/([^/]+)(?:\/([^/?]+))?/)?.slice(1);
|
|
599
|
+
if (webhook?.[0])
|
|
600
|
+
return `webhooks:${webhook.filter(Boolean).join(':')}`;
|
|
601
|
+
return '';
|
|
602
|
+
}
|
package/lib/api/bucket.js
CHANGED
|
@@ -35,7 +35,7 @@ class Bucket {
|
|
|
35
35
|
this.processing = setTimeout(() => {
|
|
36
36
|
this.processing = false;
|
|
37
37
|
this.process(true);
|
|
38
|
-
},
|
|
38
|
+
}, Math.max(0, (this.reset || 0) - now) + 1);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
--this.remaining;
|
|
@@ -60,12 +60,18 @@ class Bucket {
|
|
|
60
60
|
this.process();
|
|
61
61
|
}
|
|
62
62
|
triggerResetAfter() {
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
if (this.processingResetAfter || !this.resetAfter)
|
|
64
|
+
return;
|
|
65
|
+
this.processingResetAfter = setTimeout(() => {
|
|
66
|
+
this.remaining++;
|
|
67
|
+
this.processingResetAfter = undefined;
|
|
68
|
+
const resetTimer = this.processing;
|
|
69
|
+
if (!this.queue.length || !resetTimer || resetTimer === true)
|
|
70
|
+
return;
|
|
71
|
+
clearTimeout(resetTimer);
|
|
72
|
+
this.processing = false;
|
|
73
|
+
this.process(true);
|
|
74
|
+
}, this.resetAfter * 1.5);
|
|
69
75
|
}
|
|
70
76
|
}
|
|
71
77
|
exports.Bucket = Bucket;
|
package/lib/api/shared.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from './api';
|
|
|
3
3
|
export * from './utils/constants';
|
|
4
4
|
export * from './utils/types';
|
|
5
5
|
export { calculateUserDefaultAvatarIndex } from './utils/utils';
|
|
6
|
+
/** JSON body accepted by the REST transport, including top-level bulk arrays. */
|
|
7
|
+
export type ApiRequestBody = Record<string, any> | readonly unknown[];
|
|
6
8
|
export interface ApiHandlerOptions {
|
|
7
9
|
baseUrl?: string;
|
|
8
10
|
domain?: string;
|
|
@@ -23,7 +25,7 @@ export interface RawFile {
|
|
|
23
25
|
filename: string;
|
|
24
26
|
}
|
|
25
27
|
export interface ApiRequestOptions {
|
|
26
|
-
body?:
|
|
28
|
+
body?: ApiRequestBody;
|
|
27
29
|
query?: Record<string, any>;
|
|
28
30
|
files?: RawFile[];
|
|
29
31
|
auth?: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const DefaultUserAgent: "DiscordBot (https://seyfert.dev, v4.
|
|
1
|
+
export declare const DefaultUserAgent: "DiscordBot (https://seyfert.dev, v4.6.0)";
|
|
2
2
|
export declare const ALLOWED_EXTENSIONS: readonly ["webp", "png", "jpg", "jpeg", "gif"];
|
|
3
3
|
export declare const ALLOWED_STICKER_EXTENSIONS: readonly ["png", "json", "gif"];
|
|
4
4
|
export declare const ALLOWED_SIZES: readonly [16, 32, 64, 100, 128, 256, 512, 1024, 2048, 4096];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ALLOWED_TAG_BADGE_EXTENSIONS = exports.ALLOWED_SOUNDS_EXTENSIONS = exports.ALLOWED_SIZES = exports.ALLOWED_STICKER_EXTENSIONS = exports.ALLOWED_EXTENSIONS = exports.DefaultUserAgent = void 0;
|
|
4
|
-
exports.DefaultUserAgent = 'DiscordBot (https://seyfert.dev, v4.
|
|
4
|
+
exports.DefaultUserAgent = 'DiscordBot (https://seyfert.dev, v4.6.0)';
|
|
5
5
|
exports.ALLOWED_EXTENSIONS = ['webp', 'png', 'jpg', 'jpeg', 'gif'];
|
|
6
6
|
exports.ALLOWED_STICKER_EXTENSIONS = ['png', 'json', 'gif'];
|
|
7
7
|
exports.ALLOWED_SIZES = [16, 32, 64, 100, 128, 256, 512, 1_024, 2_048, 4_096];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RawFile, UsingClient } from '..';
|
|
2
2
|
import { type ImageResolvable, type ObjectToLower } from '../common';
|
|
3
3
|
import { Base } from '../structures/extra/Base';
|
|
4
|
-
import type
|
|
4
|
+
import { type APIAttachment, type RESTAPIAttachment } from '../types';
|
|
5
5
|
export interface AttachmentResolvableMap {
|
|
6
6
|
url: string;
|
|
7
7
|
buffer: ReadableStream | Buffer | ArrayBuffer | Uint8Array | Uint8ClampedArray;
|
|
@@ -12,6 +12,7 @@ export type AttachmentDataType = keyof AttachmentResolvableMap;
|
|
|
12
12
|
export interface AttachmentData {
|
|
13
13
|
filename: string;
|
|
14
14
|
description: string;
|
|
15
|
+
is_spoiler?: boolean;
|
|
15
16
|
resolvable: AttachmentResolvable;
|
|
16
17
|
type: AttachmentDataType;
|
|
17
18
|
}
|
|
@@ -21,6 +22,10 @@ export declare class Attachment extends Base {
|
|
|
21
22
|
data: APIAttachment;
|
|
22
23
|
constructor(client: UsingClient, data: APIAttachment);
|
|
23
24
|
toJSON(): APIAttachment;
|
|
25
|
+
/**
|
|
26
|
+
* Gets whether the attachment is a spoiler.
|
|
27
|
+
*/
|
|
28
|
+
get spoiler(): boolean;
|
|
24
29
|
}
|
|
25
30
|
export declare class AttachmentBuilder {
|
|
26
31
|
data: Partial<AttachmentData>;
|
|
@@ -15,6 +15,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
15
15
|
const utils_1 = require("../api/utils/utils");
|
|
16
16
|
const common_1 = require("../common");
|
|
17
17
|
const Base_1 = require("../structures/extra/Base");
|
|
18
|
+
const types_1 = require("../types");
|
|
18
19
|
class Attachment extends Base_1.Base {
|
|
19
20
|
data;
|
|
20
21
|
constructor(client, data) {
|
|
@@ -25,6 +26,12 @@ class Attachment extends Base_1.Base {
|
|
|
25
26
|
toJSON() {
|
|
26
27
|
return { ...this.data };
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Gets whether the attachment is a spoiler.
|
|
31
|
+
*/
|
|
32
|
+
get spoiler() {
|
|
33
|
+
return Boolean((this.data.flags ?? 0) & types_1.AttachmentFlags.IsSpoiler) || this.data.filename.startsWith('SPOILER_');
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
36
|
exports.Attachment = Attachment;
|
|
30
37
|
class AttachmentBuilder {
|
|
@@ -83,20 +90,17 @@ class AttachmentBuilder {
|
|
|
83
90
|
* attachment.setSpoiler(true);
|
|
84
91
|
*/
|
|
85
92
|
setSpoiler(spoiler) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (!spoiler) {
|
|
93
|
+
this.data.is_spoiler = spoiler;
|
|
94
|
+
if (!spoiler && this.data.filename?.startsWith('SPOILER_')) {
|
|
89
95
|
this.data.filename = this.data.filename.slice('SPOILER_'.length);
|
|
90
|
-
return this;
|
|
91
96
|
}
|
|
92
|
-
this.data.filename = `SPOILER_${this.data.filename}`;
|
|
93
97
|
return this;
|
|
94
98
|
}
|
|
95
99
|
/**
|
|
96
100
|
* Gets whether the attachment is a spoiler.
|
|
97
101
|
*/
|
|
98
102
|
get spoiler() {
|
|
99
|
-
return this.data.filename?.startsWith('SPOILER_') ?? false;
|
|
103
|
+
return this.data.is_spoiler ?? this.data.filename?.startsWith('SPOILER_') ?? false;
|
|
100
104
|
}
|
|
101
105
|
/**
|
|
102
106
|
* Converts the Attachment instance to JSON.
|
|
@@ -116,10 +120,10 @@ function resolveAttachment(resolve) {
|
|
|
116
120
|
if ('id' in resolve)
|
|
117
121
|
return resolve;
|
|
118
122
|
if (resolve instanceof AttachmentBuilder) {
|
|
119
|
-
const { filename, description } = resolve.toJSON();
|
|
120
|
-
return { filename, description };
|
|
123
|
+
const { filename, description, is_spoiler } = resolve.toJSON();
|
|
124
|
+
return { filename, description, is_spoiler };
|
|
121
125
|
}
|
|
122
|
-
return { filename: resolve.filename, description: resolve.description };
|
|
126
|
+
return { filename: resolve.filename, description: resolve.description, is_spoiler: resolve.is_spoiler };
|
|
123
127
|
}
|
|
124
128
|
/**
|
|
125
129
|
* Resolves a list of attachments to raw files.
|
|
@@ -65,7 +65,7 @@ class CheckboxGroup extends Base_1.BaseComponentBuilder {
|
|
|
65
65
|
...(0, common_1.createValidationMetadata)('number of options between 2 and 10', optionCount, {
|
|
66
66
|
component: 'CheckboxGroup',
|
|
67
67
|
}),
|
|
68
|
-
detail:
|
|
68
|
+
detail: `CheckboxGroup has an invalid options length: expected number of options between 2 and 10, received ${optionCount}.`,
|
|
69
69
|
},
|
|
70
70
|
});
|
|
71
71
|
}
|