@ovencord/rest 2.5.8 → 2.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/package.json +3 -3
- package/src/index.ts +0 -1
- package/src/lib/CDN.ts +0 -1
- package/src/lib/REST.ts +16 -19
- package/src/lib/errors/DiscordAPIError.ts +3 -3
- package/src/lib/handlers/BurstHandler.ts +2 -3
- package/src/lib/handlers/SequentialHandler.ts +4 -4
- package/src/lib/handlers/Shared.ts +2 -4
- package/src/lib/interfaces/Handler.ts +1 -2
- package/src/lib/utils/AsyncEventEmitter.ts +0 -1
- package/src/lib/utils/AsyncQueue.ts +23 -23
- package/src/lib/utils/constants.ts +2 -3
- package/src/lib/utils/types.ts +5 -4
- package/src/lib/utils/utils.ts +42 -43
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@ovencord/rest",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
5
|
"description": "The REST API for discord.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "bun test",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"homepage": "https://ovencord.dev",
|
|
37
37
|
"funding": "https://github.com/ovencord/ovencord?sponsor",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@ovencord/collection": "^2.1.
|
|
40
|
-
"@ovencord/util": "^1.1.
|
|
39
|
+
"@ovencord/collection": "^2.1.4",
|
|
40
|
+
"@ovencord/util": "^1.1.8",
|
|
41
41
|
"discord-api-types": "^0.38.40"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {},
|
package/src/index.ts
CHANGED
package/src/lib/CDN.ts
CHANGED
package/src/lib/REST.ts
CHANGED
|
@@ -1,33 +1,31 @@
|
|
|
1
1
|
import { Collection } from '@ovencord/collection';
|
|
2
2
|
import { DiscordSnowflake } from '@ovencord/util';
|
|
3
|
-
import { AsyncEventEmitter } from './utils/AsyncEventEmitter.js';
|
|
4
|
-
import { uuidv5 as uuidV5 } from './utils/utils.js';
|
|
5
3
|
import { CDN } from './CDN.js';
|
|
6
4
|
import { BurstHandler } from './handlers/BurstHandler.js';
|
|
7
5
|
import { SequentialHandler } from './handlers/SequentialHandler.js';
|
|
8
6
|
import type { IHandler } from './interfaces/Handler.js';
|
|
7
|
+
import { AsyncEventEmitter } from './utils/AsyncEventEmitter.js';
|
|
9
8
|
import {
|
|
10
9
|
AUTH_UUID_NAMESPACE,
|
|
11
10
|
BurstHandlerMajorIdKey,
|
|
12
11
|
DefaultRestOptions,
|
|
13
12
|
DefaultUserAgent,
|
|
14
|
-
|
|
15
13
|
RESTEvents,
|
|
16
14
|
} from './utils/constants.js';
|
|
17
|
-
import { RequestMethod } from './utils/types.js';
|
|
18
15
|
import type {
|
|
19
|
-
|
|
20
|
-
ResponseLike,
|
|
21
|
-
RestEvents,
|
|
16
|
+
AuthData,
|
|
22
17
|
HashData,
|
|
23
18
|
InternalRequest,
|
|
24
|
-
|
|
19
|
+
RESTOptions,
|
|
20
|
+
RequestData,
|
|
25
21
|
RequestHeaders,
|
|
22
|
+
ResponseLike,
|
|
23
|
+
RestEvents,
|
|
26
24
|
RouteData,
|
|
27
|
-
|
|
28
|
-
AuthData,
|
|
25
|
+
RouteLike,
|
|
29
26
|
} from './utils/types.js';
|
|
30
|
-
import {
|
|
27
|
+
import { RequestMethod } from './utils/types.js';
|
|
28
|
+
import { isBufferLike, parseResponse, uuidv5 as uuidV5 } from './utils/utils.js';
|
|
31
29
|
|
|
32
30
|
/**
|
|
33
31
|
* Represents the class that manages handlers for endpoints
|
|
@@ -380,15 +378,12 @@ export class REST extends AsyncEventEmitter<RestEvents> {
|
|
|
380
378
|
|
|
381
379
|
// Set the final body to the form data
|
|
382
380
|
finalBody = formData;
|
|
383
|
-
|
|
384
381
|
} else if (request.body != null) {
|
|
385
382
|
if (request.passThroughBody) {
|
|
386
383
|
finalBody = request.body as any;
|
|
387
384
|
} else {
|
|
388
385
|
// Stringify the JSON data
|
|
389
|
-
finalBody = JSON.stringify(request.body, (_, value) =>
|
|
390
|
-
typeof value === 'bigint' ? value.toString() : value,
|
|
391
|
-
);
|
|
386
|
+
finalBody = JSON.stringify(request.body, (_, value) => (typeof value === 'bigint' ? value.toString() : value));
|
|
392
387
|
// Set the additional headers to specify the content-type
|
|
393
388
|
additionalHeaders = { 'Content-Type': 'application/json' };
|
|
394
389
|
}
|
|
@@ -459,10 +454,12 @@ export class REST extends AsyncEventEmitter<RestEvents> {
|
|
|
459
454
|
// Hard-Code Old Message Deletion Exception (2 week+ old messages are a different bucket)
|
|
460
455
|
// https://github.com/discord/discord-api-docs/issues/1295
|
|
461
456
|
if (method === RequestMethod.Delete && baseRoute === '/channels/:id/messages/:id') {
|
|
462
|
-
const id = /\d{17,19}$/.exec(endpoint)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
457
|
+
const id = /\d{17,19}$/.exec(endpoint)?.[0];
|
|
458
|
+
if (id) {
|
|
459
|
+
const timestamp = DiscordSnowflake.timestampFrom(id);
|
|
460
|
+
if (Date.now() - timestamp > 1_000 * 60 * 60 * 24 * 14) {
|
|
461
|
+
exceptions += '/Delete Old Message';
|
|
462
|
+
}
|
|
466
463
|
}
|
|
467
464
|
}
|
|
468
465
|
|
|
@@ -77,7 +77,7 @@ export class DiscordAPIError extends Error {
|
|
|
77
77
|
let flattened = '';
|
|
78
78
|
if ('code' in error) {
|
|
79
79
|
if (error.errors) {
|
|
80
|
-
flattened = [...
|
|
80
|
+
flattened = [...DiscordAPIError.flattenDiscordError(error.errors)].join('\n');
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
return error.message && flattened
|
|
@@ -106,10 +106,10 @@ export class DiscordAPIError extends Error {
|
|
|
106
106
|
yield val;
|
|
107
107
|
} else if (isErrorGroupWrapper(val)) {
|
|
108
108
|
for (const error of val._errors) {
|
|
109
|
-
yield*
|
|
109
|
+
yield* DiscordAPIError.flattenDiscordError(error, nextKey);
|
|
110
110
|
}
|
|
111
111
|
} else {
|
|
112
|
-
yield*
|
|
112
|
+
yield* DiscordAPIError.flattenDiscordError(val, nextKey);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import type { REST } from '../REST.js';
|
|
3
1
|
import type { IHandler } from '../interfaces/Handler.js';
|
|
2
|
+
import type { REST } from '../REST.js';
|
|
4
3
|
import { RESTEvents } from '../utils/constants.js';
|
|
5
|
-
import type {
|
|
4
|
+
import type { HandlerRequestData, RateLimitData, ResponseLike, RouteData } from '../utils/types.js';
|
|
6
5
|
import { normalizeRateLimitOffset, onRateLimit, sleep } from '../utils/utils.js';
|
|
7
6
|
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
|
8
7
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { AsyncQueue } from '../utils/AsyncQueue.js';
|
|
2
|
-
import type { REST } from '../REST.js';
|
|
3
1
|
import type { IHandler } from '../interfaces/Handler.js';
|
|
2
|
+
import type { REST } from '../REST.js';
|
|
3
|
+
import { AsyncQueue } from '../utils/AsyncQueue.js';
|
|
4
4
|
import { RESTEvents } from '../utils/constants.js';
|
|
5
|
-
import type { RateLimitData, ResponseLike,
|
|
5
|
+
import type { HandlerRequestData, RateLimitData, ResponseLike, RouteData } from '../utils/types.js';
|
|
6
6
|
import { hasSublimit, normalizeRateLimitOffset, onRateLimit, sleep } from '../utils/utils.js';
|
|
7
7
|
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
enum QueueType {
|
|
10
10
|
Standard,
|
|
11
11
|
Sublimit,
|
|
12
12
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import type { REST } from '../REST.js';
|
|
4
1
|
import type { DiscordErrorData, OAuthErrorData } from '../errors/DiscordAPIError.js';
|
|
5
2
|
import { DiscordAPIError } from '../errors/DiscordAPIError.js';
|
|
6
3
|
import { HTTPError } from '../errors/HTTPError.js';
|
|
4
|
+
import type { REST } from '../REST.js';
|
|
7
5
|
import { RESTEvents } from '../utils/constants.js';
|
|
8
|
-
import type {
|
|
6
|
+
import type { HandlerRequestData, ResponseLike, RouteData } from '../utils/types.js';
|
|
9
7
|
import { normalizeRetryBackoff, normalizeTimeout, parseResponse, shouldRetry, sleep } from '../utils/utils.js';
|
|
10
8
|
|
|
11
9
|
let authFalseWarningEmitted = false;
|
|
@@ -17,7 +17,7 @@ export class AsyncQueue {
|
|
|
17
17
|
* @param options.signal - An optional abort signal
|
|
18
18
|
*/
|
|
19
19
|
public wait(options?: { signal?: AbortSignal }): Promise<void> {
|
|
20
|
-
const next = this.#promises.length ? this.#promises.at(-1)
|
|
20
|
+
const next = this.#promises.length ? this.#promises.at(-1)?.promise : Promise.resolve();
|
|
21
21
|
|
|
22
22
|
let resolve!: () => void;
|
|
23
23
|
const promise = new Promise<void>((res) => {
|
|
@@ -26,32 +26,32 @@ export class AsyncQueue {
|
|
|
26
26
|
|
|
27
27
|
this.#promises.push({ promise, resolve });
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
// If no signal, just return the promise we wait on
|
|
30
|
+
if (!options?.signal) {
|
|
31
|
+
return next;
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
return new Promise((res, rej) => {
|
|
35
|
+
if (options.signal?.aborted) {
|
|
36
|
+
// Bridge immediately: when next resolves, we resolve our token
|
|
37
|
+
next.then(() => resolve());
|
|
38
|
+
rej(new Error('AbortError')); // TODO: Use DOMException or standard AbortError if available
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
const abortHandler = () => {
|
|
43
|
+
// Bridge: when next resolves, we resolve our token
|
|
44
|
+
next.then(() => resolve());
|
|
45
|
+
rej(new Error('AbortError'));
|
|
46
|
+
};
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
options.signal?.addEventListener('abort', abortHandler, { once: true });
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
next.then(() => {
|
|
51
|
+
options.signal?.removeEventListener('abort', abortHandler);
|
|
52
|
+
res();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -15,7 +15,7 @@ export const DefaultUserAgent =
|
|
|
15
15
|
export const DefaultUserAgentAppendix = getUserAgentAppendix();
|
|
16
16
|
|
|
17
17
|
export const DefaultRestOptions = {
|
|
18
|
-
// @ts-
|
|
18
|
+
// @ts-expect-error
|
|
19
19
|
agent: null,
|
|
20
20
|
api: 'https://discord.com/api',
|
|
21
21
|
authPrefix: 'Bot',
|
|
@@ -24,7 +24,7 @@ export const DefaultRestOptions = {
|
|
|
24
24
|
invalidRequestWarningInterval: 0,
|
|
25
25
|
globalRequestsPerSecond: 50,
|
|
26
26
|
offset: 50,
|
|
27
|
-
// @ts-
|
|
27
|
+
// @ts-expect-error
|
|
28
28
|
rejectOnRateLimit: null,
|
|
29
29
|
retries: 3,
|
|
30
30
|
retryBackoff: 0,
|
|
@@ -61,7 +61,6 @@ export const ALLOWED_SIZES: readonly number[] = [
|
|
|
61
61
|
export type ImageExtension = (typeof ALLOWED_EXTENSIONS)[number];
|
|
62
62
|
export type StickerExtension = (typeof ALLOWED_STICKER_EXTENSIONS)[number];
|
|
63
63
|
|
|
64
|
-
|
|
65
64
|
export const BurstHandlerMajorIdKey = 'burst';
|
|
66
65
|
|
|
67
66
|
export const AUTH_UUID_NAMESPACE = 'acc82a4c-f887-417b-a69c-f74096ff7e59';
|
package/src/lib/utils/types.ts
CHANGED
|
@@ -260,10 +260,11 @@ export interface APIRequest {
|
|
|
260
260
|
route: string;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
export interface ResponseLike
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
export interface ResponseLike
|
|
264
|
+
extends Pick<
|
|
265
|
+
globalThis.Response,
|
|
266
|
+
'arrayBuffer' | 'bodyUsed' | 'headers' | 'json' | 'ok' | 'status' | 'statusText' | 'text'
|
|
267
|
+
> {
|
|
267
268
|
body: ReadableStream | null;
|
|
268
269
|
}
|
|
269
270
|
|
package/src/lib/utils/utils.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
1
|
import type { RESTPatchAPIChannelJSONBody, Snowflake } from 'discord-api-types/v10';
|
|
3
|
-
import type { REST } from '../REST.js';
|
|
4
2
|
import { RateLimitError } from '../errors/RateLimitError.js';
|
|
5
|
-
import {
|
|
3
|
+
import type { REST } from '../REST.js';
|
|
6
4
|
import type {
|
|
7
5
|
GetRateLimitOffsetFunction,
|
|
8
6
|
GetRetryBackoffFunction,
|
|
@@ -10,6 +8,7 @@ import type {
|
|
|
10
8
|
RateLimitData,
|
|
11
9
|
ResponseLike,
|
|
12
10
|
} from './types.js';
|
|
11
|
+
import { RequestMethod } from './types.js';
|
|
13
12
|
|
|
14
13
|
function serializeSearchParam(value: unknown): string | null {
|
|
15
14
|
switch (typeof value) {
|
|
@@ -203,44 +202,44 @@ export function normalizeTimeout(timeout: GetTimeoutFunction | number, route: st
|
|
|
203
202
|
* @param namespace - The namespace UUID
|
|
204
203
|
*/
|
|
205
204
|
export function uuidv5(value: string | Uint8Array, namespace: string): string {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
205
|
+
// 1. Verify namespace is a valid UUID
|
|
206
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(namespace)) {
|
|
207
|
+
throw new TypeError('Invalid namespace UUID');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 2. Parse namespace UUID into bytes
|
|
211
|
+
const namespaceBytes = new Uint8Array(16);
|
|
212
|
+
const hex = namespace.replace(/-/g, '');
|
|
213
|
+
for (let i = 0; i < 16; i++) {
|
|
214
|
+
namespaceBytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 3. Convert value to bytes if string
|
|
218
|
+
const valueBytes = typeof value === 'string' ? new TextEncoder().encode(value) : value;
|
|
219
|
+
|
|
220
|
+
// 4. Concatenate namespace and value
|
|
221
|
+
const data = new Uint8Array(namespaceBytes.length + valueBytes.length);
|
|
222
|
+
data.set(namespaceBytes);
|
|
223
|
+
data.set(valueBytes, namespaceBytes.length);
|
|
224
|
+
|
|
225
|
+
// 5. Hash with SHA-1
|
|
226
|
+
const buffer = new Bun.CryptoHasher('sha1').update(data).digest();
|
|
227
|
+
const hash = new Uint8Array(buffer);
|
|
228
|
+
|
|
229
|
+
// 6. Set version to 5 (0101)
|
|
230
|
+
hash[6] = (hash[6]! & 0x0f) | 0x50;
|
|
231
|
+
|
|
232
|
+
// 7. Set variant to RFC 4122 (10xx)
|
|
233
|
+
hash[8] = (hash[8]! & 0x3f) | 0x80;
|
|
234
|
+
|
|
235
|
+
// 8. Convert to hex string with dashes
|
|
236
|
+
const hexHash = Array.from(hash, (byte) => byte.toString(16).padStart(2, '0'));
|
|
237
|
+
|
|
238
|
+
return [
|
|
239
|
+
hexHash.slice(0, 4).join(''),
|
|
240
|
+
hexHash.slice(4, 6).join(''),
|
|
241
|
+
hexHash.slice(6, 8).join(''),
|
|
242
|
+
hexHash.slice(8, 10).join(''),
|
|
243
|
+
hexHash.slice(10, 16).join(''),
|
|
244
|
+
].join('-');
|
|
246
245
|
}
|