@discordeno/rest 19.0.0-next.f249e58 → 19.0.0-next.f34c0c3
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/dist/invalidBucket.js +6 -6
- package/dist/invalidBucket.js.map +1 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +253 -59
- package/dist/manager.js.map +1 -1
- package/dist/queue.d.ts +5 -0
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +15 -11
- package/dist/queue.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +42 -4
- package/dist/routes.js.map +1 -1
- package/dist/types.d.ts +197 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/typings/routes.d.ts +24 -4
- package/dist/typings/routes.d.ts.map +1 -1
- package/dist/typings/routes.js.map +1 -1
- package/package.json +4 -5
package/dist/invalidBucket.js
CHANGED
|
@@ -21,7 +21,7 @@ import { delay, logger } from '@discordeno/utils';
|
|
|
21
21
|
processing: false,
|
|
22
22
|
waiting: [],
|
|
23
23
|
requestsAllowed: function() {
|
|
24
|
-
if (bucket.resetAt !== undefined && Date.now()
|
|
24
|
+
if (bucket.resetAt !== undefined && Date.now() >= bucket.resetAt) {
|
|
25
25
|
bucket.invalidRequests = 0;
|
|
26
26
|
bucket.resetAt = Date.now() + bucket.interval;
|
|
27
27
|
}
|
|
@@ -45,14 +45,13 @@ import { delay, logger } from '@discordeno/utils';
|
|
|
45
45
|
},
|
|
46
46
|
processWaiting: async function() {
|
|
47
47
|
// If already processing, that loop will handle all waiting requests.
|
|
48
|
-
if (bucket.processing)
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
48
|
+
if (bucket.processing) return;
|
|
51
49
|
// Mark as processing so other loops don't start
|
|
52
50
|
bucket.processing = true;
|
|
53
51
|
while(bucket.waiting.length > 0){
|
|
54
|
-
logger.info(`[InvalidBucket] processing waiting queue while loop ran with ${bucket.waiting.length}
|
|
55
|
-
if (bucket.resetAt !== undefined
|
|
52
|
+
logger.info(`[InvalidBucket] processing waiting queue while loop ran with ${bucket.waiting.length} pending requests to be made. ${JSON.stringify(bucket)}`);
|
|
53
|
+
if (!bucket.isRequestAllowed() && bucket.resetAt !== undefined) {
|
|
54
|
+
logger.warn(`[InvalidBucket] processing waiting queue is now paused until more requests are available. ${bucket.waiting.length} pending requests. ${JSON.stringify(bucket)}`);
|
|
56
55
|
await delay(bucket.resetAt - Date.now());
|
|
57
56
|
}
|
|
58
57
|
bucket.activeRequests += 1;
|
|
@@ -74,6 +73,7 @@ import { delay, logger } from '@discordeno/utils';
|
|
|
74
73
|
bucket.resetAt = Date.now() + bucket.interval;
|
|
75
74
|
}
|
|
76
75
|
bucket.invalidRequests += 1;
|
|
76
|
+
logger.warn(`[InvalidBucket] an invalid request was made. Increasing invalidRequests count to ${bucket.invalidRequests}`);
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
return bucket;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/invalidBucket.ts"],"sourcesContent":["import { delay, logger } from '@discordeno/utils'\n\n/**\n * A invalid request bucket is used in a similar manner as a leaky bucket but a invalid request bucket can be refilled as needed.\n * It's purpose is to make sure the bot does not hit the limit to getting a 1 hr ban.\n *\n * @param options The options used to configure this bucket.\n * @returns RefillingBucket\n */\nexport function createInvalidRequestBucket(options: InvalidRequestBucketOptions): InvalidRequestBucket {\n const bucket: InvalidRequestBucket = {\n invalidRequests: options.current ?? 0,\n max: options.max ?? 10000,\n interval: options.interval ?? 600_000, // 10 minutes\n resetAt: options.resetAt,\n safety: options.safety ?? 1,\n errorStatuses: options.errorStatuses ?? [401, 403, 429],\n activeRequests: options.requested ?? 0,\n processing: false,\n\n waiting: [],\n\n requestsAllowed: function () {\n if (bucket.resetAt !== undefined && Date.now()
|
|
1
|
+
{"version":3,"sources":["../src/invalidBucket.ts"],"sourcesContent":["import { delay, logger } from '@discordeno/utils'\n\n/**\n * A invalid request bucket is used in a similar manner as a leaky bucket but a invalid request bucket can be refilled as needed.\n * It's purpose is to make sure the bot does not hit the limit to getting a 1 hr ban.\n *\n * @param options The options used to configure this bucket.\n * @returns RefillingBucket\n */\nexport function createInvalidRequestBucket(options: InvalidRequestBucketOptions): InvalidRequestBucket {\n const bucket: InvalidRequestBucket = {\n invalidRequests: options.current ?? 0,\n max: options.max ?? 10000,\n interval: options.interval ?? 600_000, // 10 minutes\n resetAt: options.resetAt,\n safety: options.safety ?? 1,\n errorStatuses: options.errorStatuses ?? [401, 403, 429],\n activeRequests: options.requested ?? 0,\n processing: false,\n\n waiting: [],\n\n requestsAllowed: function () {\n if (bucket.resetAt !== undefined && Date.now() >= bucket.resetAt) {\n bucket.invalidRequests = 0\n bucket.resetAt = Date.now() + bucket.interval\n }\n\n return bucket.max - bucket.invalidRequests - bucket.activeRequests - bucket.safety\n },\n\n isRequestAllowed: function () {\n return bucket.requestsAllowed() > 0\n },\n\n waitUntilRequestAvailable: async function () {\n // eslint-disable-next-line no-async-promise-executor\n return await new Promise(async (resolve) => {\n // If whatever amount of requests is left is more than the safety margin, allow the request\n if (bucket.isRequestAllowed()) {\n bucket.activeRequests += 1\n resolve()\n } else {\n bucket.waiting.push(resolve)\n await bucket.processWaiting()\n }\n })\n },\n\n processWaiting: async function () {\n // If already processing, that loop will handle all waiting requests.\n if (bucket.processing) return\n\n // Mark as processing so other loops don't start\n bucket.processing = true\n\n while (bucket.waiting.length > 0) {\n logger.info(`[InvalidBucket] processing waiting queue while loop ran with ${bucket.waiting.length} pending requests to be made. ${JSON.stringify(bucket)}`)\n\n if (!bucket.isRequestAllowed() && bucket.resetAt !== undefined) {\n logger.warn(`[InvalidBucket] processing waiting queue is now paused until more requests are available. ${bucket.waiting.length} pending requests. ${JSON.stringify(bucket)}`)\n await delay(bucket.resetAt - Date.now())\n }\n\n bucket.activeRequests += 1\n // Resolve the next item in the queue\n bucket.waiting.shift()?.()\n }\n\n // Mark as false so next pending request can be triggered by new loop.\n bucket.processing = false\n },\n\n handleCompletedRequest: function (code, sharedScope) {\n // Since request is complete, we can remove one from requested.\n bucket.activeRequests -= 1\n // Since it is as a valid request, we don't need to do anything\n if (!bucket.errorStatuses.includes(code)) return\n // Shared scope is not considered invalid\n if (code === 429 && sharedScope) return\n\n // INVALID REQUEST WAS MADE\n if (bucket.resetAt === undefined) {\n bucket.resetAt = Date.now() + bucket.interval\n }\n\n bucket.invalidRequests += 1\n logger.warn(`[InvalidBucket] an invalid request was made. Increasing invalidRequests count to ${bucket.invalidRequests}`)\n },\n }\n\n return bucket\n}\n\nexport interface InvalidRequestBucketOptions {\n /** current invalid amount */\n current?: number\n /** max invalid requests allowed until ban. Defaults to 10,000 */\n max?: number\n /** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */\n interval?: number\n /** When the timeout for the bucket has started at. */\n resetAt?: number\n /** how safe to be from max. Defaults to 1 */\n safety?: number\n /** The request statuses that count as an invalid request. */\n errorStatuses?: number[]\n /** The amount of requests that were requested from this bucket. */\n requested?: number\n}\n\nexport interface InvalidRequestBucket {\n /** current invalid amount */\n invalidRequests: number\n /** max invalid requests allowed until ban. Defaults to 10,000 */\n max: number\n /** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */\n interval: number\n /** When the timeout for this bucket has started at. */\n resetAt: number | undefined\n /** how safe to be from max. Defaults to 1 */\n safety: number\n /** The request statuses that count as an invalid request. */\n errorStatuses: number[]\n /** The amount of requests that were requested from this bucket. */\n activeRequests: number\n /** The requests that are currently pending. */\n waiting: Array<(value: void | PromiseLike<void>) => void>\n /** Whether or not the waiting queue is already processing. */\n processing: boolean\n\n /** Gives the number of requests that are currently allowed. */\n requestsAllowed: () => number\n /** Checks if a request is allowed at this time. */\n isRequestAllowed: () => boolean\n /** Waits until a request is available */\n waitUntilRequestAvailable: () => Promise<void>\n /** Begins processing the waiting queue of requests. */\n processWaiting: () => Promise<void>\n /** Handler for whenever a request is validated. This should update the requested values or trigger any other necessary stuff. */\n handleCompletedRequest: (code: number, sharedScope: boolean) => void\n}\n"],"names":["delay","logger","createInvalidRequestBucket","options","bucket","invalidRequests","current","max","interval","resetAt","safety","errorStatuses","activeRequests","requested","processing","waiting","requestsAllowed","undefined","Date","now","isRequestAllowed","waitUntilRequestAvailable","Promise","resolve","push","processWaiting","length","info","JSON","stringify","warn","shift","handleCompletedRequest","code","sharedScope","includes"],"mappings":"AAAA,SAASA,KAAK,EAAEC,MAAM,QAAQ,oBAAmB;AAEjD;;;;;;CAMC,GACD,OAAO,SAASC,2BAA2BC,OAAoC,EAAwB;IACrG,MAAMC,SAA+B;QACnCC,iBAAiBF,QAAQG,OAAO,IAAI;QACpCC,KAAKJ,QAAQI,GAAG,IAAI;QACpBC,UAAUL,QAAQK,QAAQ,IAAI;QAC9BC,SAASN,QAAQM,OAAO;QACxBC,QAAQP,QAAQO,MAAM,IAAI;QAC1BC,eAAeR,QAAQQ,aAAa,IAAI;YAAC;YAAK;YAAK;SAAI;QACvDC,gBAAgBT,QAAQU,SAAS,IAAI;QACrCC,YAAY,KAAK;QAEjBC,SAAS,EAAE;QAEXC,iBAAiB,WAAY;YAC3B,IAAIZ,OAAOK,OAAO,KAAKQ,aAAaC,KAAKC,GAAG,MAAMf,OAAOK,OAAO,EAAE;gBAChEL,OAAOC,eAAe,GAAG;gBACzBD,OAAOK,OAAO,GAAGS,KAAKC,GAAG,KAAKf,OAAOI,QAAQ;YAC/C,CAAC;YAED,OAAOJ,OAAOG,GAAG,GAAGH,OAAOC,eAAe,GAAGD,OAAOQ,cAAc,GAAGR,OAAOM,MAAM;QACpF;QAEAU,kBAAkB,WAAY;YAC5B,OAAOhB,OAAOY,eAAe,KAAK;QACpC;QAEAK,2BAA2B,iBAAkB;YAC3C,qDAAqD;YACrD,OAAO,MAAM,IAAIC,QAAQ,OAAOC,UAAY;gBAC1C,2FAA2F;gBAC3F,IAAInB,OAAOgB,gBAAgB,IAAI;oBAC7BhB,OAAOQ,cAAc,IAAI;oBACzBW;gBACF,OAAO;oBACLnB,OAAOW,OAAO,CAACS,IAAI,CAACD;oBACpB,MAAMnB,OAAOqB,cAAc;gBAC7B,CAAC;YACH;QACF;QAEAA,gBAAgB,iBAAkB;YAChC,qEAAqE;YACrE,IAAIrB,OAAOU,UAAU,EAAE;YAEvB,gDAAgD;YAChDV,OAAOU,UAAU,GAAG,IAAI;YAExB,MAAOV,OAAOW,OAAO,CAACW,MAAM,GAAG,EAAG;gBAChCzB,OAAO0B,IAAI,CAAC,CAAC,6DAA6D,EAAEvB,OAAOW,OAAO,CAACW,MAAM,CAAC,8BAA8B,EAAEE,KAAKC,SAAS,CAACzB,QAAQ,CAAC;gBAE1J,IAAI,CAACA,OAAOgB,gBAAgB,MAAMhB,OAAOK,OAAO,KAAKQ,WAAW;oBAC9DhB,OAAO6B,IAAI,CAAC,CAAC,0FAA0F,EAAE1B,OAAOW,OAAO,CAACW,MAAM,CAAC,mBAAmB,EAAEE,KAAKC,SAAS,CAACzB,QAAQ,CAAC;oBAC5K,MAAMJ,MAAMI,OAAOK,OAAO,GAAGS,KAAKC,GAAG;gBACvC,CAAC;gBAEDf,OAAOQ,cAAc,IAAI;gBACzB,qCAAqC;gBACrCR,OAAOW,OAAO,CAACgB,KAAK;YACtB;YAEA,sEAAsE;YACtE3B,OAAOU,UAAU,GAAG,KAAK;QAC3B;QAEAkB,wBAAwB,SAAUC,IAAI,EAAEC,WAAW,EAAE;YACnD,+DAA+D;YAC/D9B,OAAOQ,cAAc,IAAI;YACzB,+DAA+D;YAC/D,IAAI,CAACR,OAAOO,aAAa,CAACwB,QAAQ,CAACF,OAAO;YAC1C,yCAAyC;YACzC,IAAIA,SAAS,OAAOC,aAAa;YAEjC,2BAA2B;YAC3B,IAAI9B,OAAOK,OAAO,KAAKQ,WAAW;gBAChCb,OAAOK,OAAO,GAAGS,KAAKC,GAAG,KAAKf,OAAOI,QAAQ;YAC/C,CAAC;YAEDJ,OAAOC,eAAe,IAAI;YAC1BJ,OAAO6B,IAAI,CAAC,CAAC,iFAAiF,EAAE1B,OAAOC,eAAe,CAAC,CAAC;QAC1H;IACF;IAEA,OAAOD;AACT,CAAC"}
|
package/dist/manager.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAyDA,OAAO,KAAK,EAA4B,wBAAwB,EAAsB,WAAW,EAAsB,MAAM,YAAY,CAAA;AAKzI,eAAO,MAAM,mBAAmB,KAAK,CAAA;AACrC,eAAO,MAAM,eAAe,4BAA4B,CAAA;AAExD,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAC3D,eAAO,MAAM,2BAA2B,0BAA0B,CAAA;AAClE,eAAO,MAAM,6BAA6B,4BAA4B,CAAA;AACtE,eAAO,MAAM,wBAAwB,uBAAuB,CAAA;AAC5D,eAAO,MAAM,wBAAwB,uBAAuB,CAAA;AAC5D,eAAO,MAAM,uBAAuB,sBAAsB,CAAA;AAC1D,eAAO,MAAM,uBAAuB,sBAAsB,CAAA;AAE1D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,WAAW,CA62ChF"}
|
package/dist/manager.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-const-assign */ import {
|
|
2
|
-
import { calculateBits,
|
|
3
|
-
import fetch from 'node-fetch';
|
|
1
|
+
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-const-assign */ import { Buffer } from 'node:buffer';
|
|
2
|
+
import { calculateBits, camelToSnakeCase, camelize, delay, getBotIdFromToken, logger, processReactionString, urlToBase64 } from '@discordeno/utils';
|
|
4
3
|
import { createInvalidRequestBucket } from './invalidBucket.js';
|
|
5
4
|
import { Queue } from './queue.js';
|
|
5
|
+
import { InteractionResponseTypes } from '@discordeno/types';
|
|
6
6
|
import { createRoutes } from './routes.js';
|
|
7
7
|
// TODO: make dynamic based on package.json file
|
|
8
8
|
const version = '19.0.0-alpha.1';
|
|
@@ -36,8 +36,9 @@ export function createRestManager(options) {
|
|
|
36
36
|
token: options.token,
|
|
37
37
|
version: options.version ?? DISCORD_API_VERSION,
|
|
38
38
|
routes: createRoutes(),
|
|
39
|
-
checkRateLimits (url) {
|
|
40
|
-
const
|
|
39
|
+
checkRateLimits (url, headers) {
|
|
40
|
+
const authHeader = headers?.authorization ?? '';
|
|
41
|
+
const ratelimited = rest.rateLimitedPaths.get(`${authHeader}${url}`);
|
|
41
42
|
const global = rest.rateLimitedPaths.get('global');
|
|
42
43
|
const now = Date.now();
|
|
43
44
|
if (ratelimited && now < ratelimited.resetTimestamp) {
|
|
@@ -56,20 +57,27 @@ export function createRestManager(options) {
|
|
|
56
57
|
}
|
|
57
58
|
const newObj = {};
|
|
58
59
|
for (const key of Object.keys(obj)){
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
const value = obj[key];
|
|
61
|
+
// Some falsy values should be allowed like null or 0
|
|
62
|
+
if (value !== undefined) {
|
|
63
|
+
switch(key){
|
|
64
|
+
case 'permissions':
|
|
65
|
+
case 'allow':
|
|
66
|
+
case 'deny':
|
|
67
|
+
newObj[key] = typeof value === 'string' ? value : calculateBits(value);
|
|
68
|
+
continue;
|
|
69
|
+
case 'defaultMemberPermissions':
|
|
70
|
+
newObj.default_member_permissions = typeof value === 'string' ? value : calculateBits(value);
|
|
71
|
+
continue;
|
|
72
|
+
case 'nameLocalizations':
|
|
73
|
+
newObj.name_localizations = value;
|
|
74
|
+
continue;
|
|
75
|
+
case 'descriptionLocalizations':
|
|
76
|
+
newObj.description_localizations = value;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
|
-
|
|
69
|
-
newObj.default_member_permissions = calculateBits(obj[key]);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
newObj[camelToSnakeCase(key)] = rest.changeToDiscordFormat(obj[key]);
|
|
80
|
+
newObj[camelToSnakeCase(key)] = rest.changeToDiscordFormat(value);
|
|
73
81
|
}
|
|
74
82
|
return newObj;
|
|
75
83
|
}
|
|
@@ -80,7 +88,7 @@ export function createRestManager(options) {
|
|
|
80
88
|
const headers = {
|
|
81
89
|
'user-agent': `DiscordBot (https://github.com/discordeno/discordeno, v${version})`
|
|
82
90
|
};
|
|
83
|
-
if (options?.unauthorized !==
|
|
91
|
+
if (options?.unauthorized !== true) headers.authorization = `Bot ${rest.token}`;
|
|
84
92
|
// IF A REASON IS PROVIDED ENCODE IT IN HEADERS
|
|
85
93
|
if (options?.reason !== undefined) {
|
|
86
94
|
headers[AUDIT_LOG_REASON_HEADER] = encodeURIComponent(options?.reason);
|
|
@@ -94,12 +102,21 @@ export function createRestManager(options) {
|
|
|
94
102
|
for(let i = 0; i < options.files.length; ++i){
|
|
95
103
|
form.append(`file${i}`, options.files[i].blob, options.files[i].name);
|
|
96
104
|
}
|
|
97
|
-
|
|
105
|
+
// Have to use changeToDiscordFormat or else JSON.stringify may throw an error for the presence of BigInt(s) in the json
|
|
106
|
+
form.append('payload_json', JSON.stringify(rest.changeToDiscordFormat({
|
|
98
107
|
...options.body,
|
|
99
108
|
files: undefined
|
|
100
|
-
}));
|
|
109
|
+
})));
|
|
110
|
+
// No need to set the `content-type` header since `fetch` does that automatically for us when we use a `FormData` object.
|
|
101
111
|
body = form;
|
|
102
|
-
|
|
112
|
+
} else if (options?.body && options.headers && options.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
|
113
|
+
// OAuth2 body handling
|
|
114
|
+
const formBody = [];
|
|
115
|
+
const discordBody = rest.changeToDiscordFormat(options.body);
|
|
116
|
+
for(const prop in discordBody){
|
|
117
|
+
formBody.push(`${encodeURIComponent(prop)}=${encodeURIComponent(discordBody[prop])}`);
|
|
118
|
+
}
|
|
119
|
+
body = formBody.join('&');
|
|
103
120
|
} else if (options?.body !== undefined) {
|
|
104
121
|
if (options.body instanceof FormData) {
|
|
105
122
|
body = options.body;
|
|
@@ -146,7 +163,7 @@ export function createRestManager(options) {
|
|
|
146
163
|
}, 1000);
|
|
147
164
|
}
|
|
148
165
|
},
|
|
149
|
-
/** Processes the rate limit headers and determines if it needs to be rate limited and returns the bucket id if available */ processHeaders (url, headers) {
|
|
166
|
+
/** Processes the rate limit headers and determines if it needs to be rate limited and returns the bucket id if available */ processHeaders (url, headers, requestAuthorization) {
|
|
150
167
|
let rateLimited = false;
|
|
151
168
|
// GET ALL NECESSARY HEADERS
|
|
152
169
|
const remaining = headers.get(RATE_LIMIT_REMAINING_HEADER);
|
|
@@ -156,7 +173,7 @@ export function createRestManager(options) {
|
|
|
156
173
|
// undefined override null needed for typings
|
|
157
174
|
const bucketId = headers.get(RATE_LIMIT_BUCKET_HEADER) ?? undefined;
|
|
158
175
|
const limit = headers.get(RATE_LIMIT_LIMIT_HEADER);
|
|
159
|
-
rest.queues.get(url)?.handleCompletedRequest({
|
|
176
|
+
rest.queues.get(`${requestAuthorization}${url}`)?.handleCompletedRequest({
|
|
160
177
|
remaining: remaining ? Number(remaining) : undefined,
|
|
161
178
|
interval: retryAfter ? Number(retryAfter) * 1000 : undefined,
|
|
162
179
|
max: limit ? Number(limit) : undefined
|
|
@@ -165,14 +182,14 @@ export function createRestManager(options) {
|
|
|
165
182
|
if (remaining === '0') {
|
|
166
183
|
rateLimited = true;
|
|
167
184
|
// SAVE THE URL AS LIMITED, IMPORTANT FOR NEW REQUESTS BY USER WITHOUT BUCKET
|
|
168
|
-
rest.rateLimitedPaths.set(url
|
|
185
|
+
rest.rateLimitedPaths.set(`${requestAuthorization}${url}`, {
|
|
169
186
|
url,
|
|
170
187
|
resetTimestamp: reset,
|
|
171
188
|
bucketId
|
|
172
189
|
});
|
|
173
190
|
// SAVE THE BUCKET AS LIMITED SINCE DIFFERENT URLS MAY SHARE A BUCKET
|
|
174
191
|
if (bucketId) {
|
|
175
|
-
rest.rateLimitedPaths.set(bucketId
|
|
192
|
+
rest.rateLimitedPaths.set(`${requestAuthorization}${bucketId}`, {
|
|
176
193
|
url,
|
|
177
194
|
resetTimestamp: reset,
|
|
178
195
|
bucketId
|
|
@@ -181,8 +198,8 @@ export function createRestManager(options) {
|
|
|
181
198
|
}
|
|
182
199
|
// IF THERE IS NO REMAINING GLOBAL LIMIT, MARK IT RATE LIMITED GLOBALLY
|
|
183
200
|
if (global) {
|
|
184
|
-
const retryAfter = headers.get('retry-after');
|
|
185
|
-
const globalReset = Date.now() +
|
|
201
|
+
const retryAfter = Number(headers.get('retry-after')) * 1000;
|
|
202
|
+
const globalReset = Date.now() + retryAfter;
|
|
186
203
|
// rest.debug(
|
|
187
204
|
// `[REST = Globally Rate Limited] URL: ${url} | Global Rest: ${globalReset}`
|
|
188
205
|
// )
|
|
@@ -190,14 +207,14 @@ export function createRestManager(options) {
|
|
|
190
207
|
rateLimited = true;
|
|
191
208
|
setTimeout(()=>{
|
|
192
209
|
rest.globallyRateLimited = false;
|
|
193
|
-
},
|
|
210
|
+
}, retryAfter);
|
|
194
211
|
rest.rateLimitedPaths.set('global', {
|
|
195
212
|
url: 'global',
|
|
196
213
|
resetTimestamp: globalReset,
|
|
197
214
|
bucketId
|
|
198
215
|
});
|
|
199
216
|
if (bucketId) {
|
|
200
|
-
rest.rateLimitedPaths.set(bucketId
|
|
217
|
+
rest.rateLimitedPaths.set(`${requestAuthorization}${bucketId}`, {
|
|
201
218
|
url: 'global',
|
|
202
219
|
resetTimestamp: globalReset,
|
|
203
220
|
bucketId
|
|
@@ -212,17 +229,33 @@ export function createRestManager(options) {
|
|
|
212
229
|
async sendRequest (options) {
|
|
213
230
|
const url = `${rest.baseUrl}/v${rest.version}${options.route}`;
|
|
214
231
|
const payload = rest.createRequestBody(options.method, options.requestBodyOptions);
|
|
232
|
+
const loggingHeaders = {
|
|
233
|
+
...payload.headers
|
|
234
|
+
};
|
|
235
|
+
const authenticationScheme = payload.headers.authorization?.split(' ')[0];
|
|
236
|
+
if (payload.headers.authorization) {
|
|
237
|
+
loggingHeaders.authorization = `${authenticationScheme} tokenhere`;
|
|
238
|
+
}
|
|
215
239
|
logger.debug(`sending request to ${url}`, 'with payload:', {
|
|
216
240
|
...payload,
|
|
217
|
-
headers:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
241
|
+
headers: loggingHeaders
|
|
242
|
+
});
|
|
243
|
+
const response = await fetch(url, payload).catch(async (error)=>{
|
|
244
|
+
logger.error(error);
|
|
245
|
+
// Mark request and completed
|
|
246
|
+
rest.invalidBucket.handleCompletedRequest(999, false);
|
|
247
|
+
options.reject({
|
|
248
|
+
ok: false,
|
|
249
|
+
status: 999,
|
|
250
|
+
error: 'Possible network or request shape issue occurred. If this is rare, its a network glitch. If it occurs a lot something is wrong.'
|
|
251
|
+
});
|
|
252
|
+
throw error;
|
|
221
253
|
});
|
|
222
|
-
const response = await fetch(url, payload);
|
|
223
254
|
logger.debug(`request fetched from ${url} with status ${response.status} & ${response.statusText}`);
|
|
255
|
+
// Mark request and completed
|
|
256
|
+
rest.invalidBucket.handleCompletedRequest(response.status, response.headers.get(RATE_LIMIT_SCOPE_HEADER) === 'shared');
|
|
224
257
|
// Set the bucket id if it was available on the headers
|
|
225
|
-
const bucketId = rest.processHeaders(rest.simplifyUrl(options.route, options.method), response.headers);
|
|
258
|
+
const bucketId = rest.processHeaders(rest.simplifyUrl(options.route, options.method), response.headers, authenticationScheme === 'Bearer' ? payload.headers.authorization : '');
|
|
226
259
|
if (bucketId) options.bucketId = bucketId;
|
|
227
260
|
if (response.status < 200 || response.status >= 400) {
|
|
228
261
|
logger.debug(`Request to ${url} failed.`);
|
|
@@ -247,8 +280,6 @@ export function createRestManager(options) {
|
|
|
247
280
|
return;
|
|
248
281
|
}
|
|
249
282
|
options.retryCount += 1;
|
|
250
|
-
// Rate limited, add back to queue
|
|
251
|
-
rest.invalidBucket.handleCompletedRequest(response.status, response.headers.get(RATE_LIMIT_SCOPE_HEADER) === 'shared');
|
|
252
283
|
const resetAfter = response.headers.get(RATE_LIMIT_RESET_AFTER_HEADER);
|
|
253
284
|
if (resetAfter) await delay(Number(resetAfter) * 1000);
|
|
254
285
|
// process the response to prevent mem leak
|
|
@@ -289,19 +320,21 @@ export function createRestManager(options) {
|
|
|
289
320
|
await rest.sendRequest(request);
|
|
290
321
|
return;
|
|
291
322
|
}
|
|
292
|
-
const
|
|
323
|
+
const authHeader = request.requestBodyOptions?.headers?.authorization ?? '';
|
|
324
|
+
const queue = rest.queues.get(`${authHeader}${url}`);
|
|
293
325
|
if (queue !== undefined) {
|
|
294
326
|
queue.makeRequest(request);
|
|
295
327
|
} else {
|
|
296
328
|
// CREATES A NEW QUEUE
|
|
297
329
|
const bucketQueue = new Queue(rest, {
|
|
298
330
|
url,
|
|
299
|
-
deleteQueueDelay: rest.deleteQueueDelay
|
|
331
|
+
deleteQueueDelay: rest.deleteQueueDelay,
|
|
332
|
+
authentication: authHeader
|
|
300
333
|
});
|
|
301
334
|
// Add request to queue
|
|
302
335
|
bucketQueue.makeRequest(request);
|
|
303
336
|
// Save queue
|
|
304
|
-
rest.queues.set(url
|
|
337
|
+
rest.queues.set(`${authHeader}${url}`, bucketQueue);
|
|
305
338
|
}
|
|
306
339
|
},
|
|
307
340
|
async makeRequest (method, route, options) {
|
|
@@ -377,6 +410,11 @@ export function createRestManager(options) {
|
|
|
377
410
|
async addThreadMember (channelId, userId) {
|
|
378
411
|
await rest.put(rest.routes.channels.threads.user(channelId, userId));
|
|
379
412
|
},
|
|
413
|
+
async addDmRecipient (channelId, userId, body) {
|
|
414
|
+
await rest.put(rest.routes.channels.dmRecipient(channelId, userId), {
|
|
415
|
+
body
|
|
416
|
+
});
|
|
417
|
+
},
|
|
380
418
|
async createAutomodRule (guildId, body, reason) {
|
|
381
419
|
return await rest.post(rest.routes.guilds.automod.rules(guildId), {
|
|
382
420
|
body,
|
|
@@ -395,20 +433,34 @@ export function createRestManager(options) {
|
|
|
395
433
|
reason
|
|
396
434
|
});
|
|
397
435
|
},
|
|
398
|
-
async createGlobalApplicationCommand (body) {
|
|
399
|
-
|
|
436
|
+
async createGlobalApplicationCommand (body, options) {
|
|
437
|
+
const restOptions = {
|
|
400
438
|
body
|
|
401
|
-
}
|
|
439
|
+
};
|
|
440
|
+
if (options?.bearerToken) {
|
|
441
|
+
restOptions.unauthorized = true;
|
|
442
|
+
restOptions.headers = {
|
|
443
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return await rest.post(rest.routes.interactions.commands.commands(rest.applicationId), restOptions);
|
|
402
447
|
},
|
|
403
448
|
async createGuild (body) {
|
|
404
449
|
return await rest.post(rest.routes.guilds.all(), {
|
|
405
450
|
body
|
|
406
451
|
});
|
|
407
452
|
},
|
|
408
|
-
async createGuildApplicationCommand (body, guildId) {
|
|
409
|
-
|
|
453
|
+
async createGuildApplicationCommand (body, guildId, options) {
|
|
454
|
+
const restOptions = {
|
|
410
455
|
body
|
|
411
|
-
}
|
|
456
|
+
};
|
|
457
|
+
if (options?.bearerToken) {
|
|
458
|
+
restOptions.unauthorized = true;
|
|
459
|
+
restOptions.headers = {
|
|
460
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return await rest.post(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), restOptions);
|
|
412
464
|
},
|
|
413
465
|
async createGuildFromTemplate (templateCode, body) {
|
|
414
466
|
if (body.icon) {
|
|
@@ -601,7 +653,7 @@ export function createRestManager(options) {
|
|
|
601
653
|
},
|
|
602
654
|
async editBotProfile (options) {
|
|
603
655
|
const avatar = options?.botAvatarURL ? await urlToBase64(options?.botAvatarURL) : options?.botAvatarURL;
|
|
604
|
-
return await rest.patch(rest.routes.
|
|
656
|
+
return await rest.patch(rest.routes.currentUser(), {
|
|
605
657
|
body: {
|
|
606
658
|
username: options.username?.trim(),
|
|
607
659
|
avatar
|
|
@@ -675,7 +727,8 @@ export function createRestManager(options) {
|
|
|
675
727
|
},
|
|
676
728
|
async editMessage (channelId, messageId, body) {
|
|
677
729
|
return await rest.patch(rest.routes.channels.message(channelId, messageId), {
|
|
678
|
-
body
|
|
730
|
+
body,
|
|
731
|
+
files: body.files
|
|
679
732
|
});
|
|
680
733
|
},
|
|
681
734
|
async editOriginalInteractionResponse (token, body) {
|
|
@@ -777,14 +830,60 @@ export function createRestManager(options) {
|
|
|
777
830
|
async getActiveThreads (guildId) {
|
|
778
831
|
return await rest.get(rest.routes.channels.threads.active(guildId));
|
|
779
832
|
},
|
|
780
|
-
async getApplicationCommandPermission (guildId, commandId) {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
833
|
+
async getApplicationCommandPermission (guildId, commandId, options) {
|
|
834
|
+
const restOptions = {};
|
|
835
|
+
if (options?.accessToken) {
|
|
836
|
+
restOptions.unauthorized = true;
|
|
837
|
+
restOptions.headers = {
|
|
838
|
+
authorization: `Bearer ${options.accessToken}`
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
return await rest.get(rest.routes.interactions.commands.permission(options?.applicationId ?? rest.applicationId, guildId, commandId), restOptions);
|
|
842
|
+
},
|
|
843
|
+
async getApplicationCommandPermissions (guildId, options) {
|
|
844
|
+
const restOptions = {};
|
|
845
|
+
if (options?.accessToken) {
|
|
846
|
+
restOptions.unauthorized = true;
|
|
847
|
+
restOptions.headers = {
|
|
848
|
+
authorization: `Bearer ${options.accessToken}`
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
return await rest.get(rest.routes.interactions.commands.permissions(options?.applicationId ?? rest.applicationId, guildId), restOptions);
|
|
785
852
|
},
|
|
786
853
|
async getApplicationInfo () {
|
|
787
|
-
return await rest.get(rest.routes.
|
|
854
|
+
return await rest.get(rest.routes.oauth2.application());
|
|
855
|
+
},
|
|
856
|
+
async getCurrentAuthenticationInfo (token) {
|
|
857
|
+
return await rest.get(rest.routes.oauth2.currentAuthorization(), {
|
|
858
|
+
headers: {
|
|
859
|
+
authorization: `Bearer ${token}`
|
|
860
|
+
},
|
|
861
|
+
unauthorized: true
|
|
862
|
+
});
|
|
863
|
+
},
|
|
864
|
+
async exchangeToken (body) {
|
|
865
|
+
const restOptions = {
|
|
866
|
+
body,
|
|
867
|
+
headers: {
|
|
868
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
869
|
+
},
|
|
870
|
+
unauthorized: true
|
|
871
|
+
};
|
|
872
|
+
if (body.grantType === 'client_credentials') {
|
|
873
|
+
const basicCredentials = Buffer.from(`${body.clientId}:${body.clientSecret}`);
|
|
874
|
+
restOptions.headers.authorization = `Basic ${basicCredentials.toString('base64')}`;
|
|
875
|
+
restOptions.body.scope = body.scope.join(' ');
|
|
876
|
+
}
|
|
877
|
+
return await rest.post(rest.routes.oauth2.tokenExchange(), restOptions);
|
|
878
|
+
},
|
|
879
|
+
async revokeToken (body) {
|
|
880
|
+
await rest.post(rest.routes.oauth2.tokenRevoke(), {
|
|
881
|
+
body,
|
|
882
|
+
headers: {
|
|
883
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
884
|
+
},
|
|
885
|
+
unauthorized: true
|
|
886
|
+
});
|
|
788
887
|
},
|
|
789
888
|
async getAuditLog (guildId, options) {
|
|
790
889
|
return await rest.get(rest.routes.guilds.auditlogs(guildId, options));
|
|
@@ -823,6 +922,11 @@ export function createRestManager(options) {
|
|
|
823
922
|
}
|
|
824
923
|
});
|
|
825
924
|
},
|
|
925
|
+
async getGroupDmChannel (body) {
|
|
926
|
+
return await rest.post(rest.routes.channels.dm(), {
|
|
927
|
+
body
|
|
928
|
+
});
|
|
929
|
+
},
|
|
826
930
|
async getEmoji (guildId, emojiId) {
|
|
827
931
|
return await rest.get(rest.routes.guilds.emoji(guildId, emojiId));
|
|
828
932
|
},
|
|
@@ -848,6 +952,14 @@ export function createRestManager(options) {
|
|
|
848
952
|
}) {
|
|
849
953
|
return await rest.get(rest.routes.guilds.guild(guildId, options.counts));
|
|
850
954
|
},
|
|
955
|
+
async getGuilds (token, options) {
|
|
956
|
+
return await rest.get(rest.routes.guilds.userGuilds(options), {
|
|
957
|
+
headers: {
|
|
958
|
+
authorization: `Bearer ${token}`
|
|
959
|
+
},
|
|
960
|
+
unauthorized: true
|
|
961
|
+
});
|
|
962
|
+
},
|
|
851
963
|
async getGuildApplicationCommand (commandId, guildId) {
|
|
852
964
|
return await rest.get(rest.routes.interactions.commands.guilds.one(rest.applicationId, guildId, commandId));
|
|
853
965
|
},
|
|
@@ -943,6 +1055,30 @@ export function createRestManager(options) {
|
|
|
943
1055
|
async getUser (id) {
|
|
944
1056
|
return await rest.get(rest.routes.user(id));
|
|
945
1057
|
},
|
|
1058
|
+
async getCurrentUser (token) {
|
|
1059
|
+
return await rest.get(rest.routes.currentUser(), {
|
|
1060
|
+
headers: {
|
|
1061
|
+
authorization: `Bearer ${token}`
|
|
1062
|
+
},
|
|
1063
|
+
unauthorized: true
|
|
1064
|
+
});
|
|
1065
|
+
},
|
|
1066
|
+
async getUserConnections (token) {
|
|
1067
|
+
return await rest.get(rest.routes.oauth2.connections(), {
|
|
1068
|
+
headers: {
|
|
1069
|
+
authorization: `Bearer ${token}`
|
|
1070
|
+
},
|
|
1071
|
+
unauthorized: true
|
|
1072
|
+
});
|
|
1073
|
+
},
|
|
1074
|
+
async getUserApplicationRoleConnection (token, applicationId) {
|
|
1075
|
+
return await rest.get(rest.routes.oauth2.roleConnections(applicationId), {
|
|
1076
|
+
headers: {
|
|
1077
|
+
authorization: `Bearer ${token}`
|
|
1078
|
+
},
|
|
1079
|
+
unauthorized: true
|
|
1080
|
+
});
|
|
1081
|
+
},
|
|
946
1082
|
async getVanityUrl (guildId) {
|
|
947
1083
|
return await rest.get(rest.routes.guilds.vanity(guildId));
|
|
948
1084
|
},
|
|
@@ -987,6 +1123,9 @@ export function createRestManager(options) {
|
|
|
987
1123
|
async removeThreadMember (channelId, userId) {
|
|
988
1124
|
await rest.delete(rest.routes.channels.threads.user(channelId, userId));
|
|
989
1125
|
},
|
|
1126
|
+
async removeDmRecipient (channelId, userId) {
|
|
1127
|
+
await rest.delete(rest.routes.channels.dmRecipient(channelId, userId));
|
|
1128
|
+
},
|
|
990
1129
|
async sendFollowupMessage (token, options) {
|
|
991
1130
|
return await rest.post(rest.routes.webhooks.webhook(rest.applicationId, token), {
|
|
992
1131
|
body: options,
|
|
@@ -1044,6 +1183,14 @@ export function createRestManager(options) {
|
|
|
1044
1183
|
async getMember (guildId, userId) {
|
|
1045
1184
|
return await rest.get(rest.routes.guilds.members.member(guildId, userId));
|
|
1046
1185
|
},
|
|
1186
|
+
async getCurrentMember (guildId, token) {
|
|
1187
|
+
return await rest.get(rest.routes.guilds.members.currentMember(guildId), {
|
|
1188
|
+
headers: {
|
|
1189
|
+
authorization: `Bearer ${token}`
|
|
1190
|
+
},
|
|
1191
|
+
unauthorized: true
|
|
1192
|
+
});
|
|
1193
|
+
},
|
|
1047
1194
|
async getMembers (guildId, options) {
|
|
1048
1195
|
return await rest.get(rest.routes.guilds.members.members(guildId, options));
|
|
1049
1196
|
},
|
|
@@ -1079,15 +1226,62 @@ export function createRestManager(options) {
|
|
|
1079
1226
|
async triggerTypingIndicator (channelId) {
|
|
1080
1227
|
await rest.post(rest.routes.channels.typing(channelId));
|
|
1081
1228
|
},
|
|
1082
|
-
async upsertGlobalApplicationCommands (body) {
|
|
1083
|
-
|
|
1229
|
+
async upsertGlobalApplicationCommands (body, options) {
|
|
1230
|
+
const restOptions = {
|
|
1231
|
+
body
|
|
1232
|
+
};
|
|
1233
|
+
if (options?.bearerToken) {
|
|
1234
|
+
restOptions.unauthorized = true;
|
|
1235
|
+
restOptions.headers = {
|
|
1236
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
return await rest.put(rest.routes.interactions.commands.commands(rest.applicationId), restOptions);
|
|
1240
|
+
},
|
|
1241
|
+
async upsertGuildApplicationCommands (guildId, body, options) {
|
|
1242
|
+
const restOptions = {
|
|
1084
1243
|
body
|
|
1244
|
+
};
|
|
1245
|
+
if (options?.bearerToken) {
|
|
1246
|
+
restOptions.unauthorized = true;
|
|
1247
|
+
restOptions.headers = {
|
|
1248
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
return await rest.put(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), restOptions);
|
|
1252
|
+
},
|
|
1253
|
+
async editUserApplicationRoleConnection (token, applicationId, body) {
|
|
1254
|
+
return await rest.put(rest.routes.oauth2.roleConnections(applicationId), {
|
|
1255
|
+
body,
|
|
1256
|
+
headers: {
|
|
1257
|
+
authorization: `Bearer ${token}`
|
|
1258
|
+
},
|
|
1259
|
+
unauthorized: true
|
|
1085
1260
|
});
|
|
1086
1261
|
},
|
|
1087
|
-
async
|
|
1088
|
-
return await rest.put(rest.routes.
|
|
1262
|
+
async addGuildMember (guildId, userId, body) {
|
|
1263
|
+
return await rest.put(rest.routes.guilds.members.member(guildId, userId), {
|
|
1089
1264
|
body
|
|
1090
1265
|
});
|
|
1266
|
+
},
|
|
1267
|
+
preferSnakeCase (enabled) {
|
|
1268
|
+
const camelizer = enabled ? (x)=>x : camelize;
|
|
1269
|
+
rest.get = async (url, options)=>{
|
|
1270
|
+
return camelizer(await rest.makeRequest('GET', url, options));
|
|
1271
|
+
};
|
|
1272
|
+
rest.post = async (url, options)=>{
|
|
1273
|
+
return camelizer(await rest.makeRequest('POST', url, options));
|
|
1274
|
+
};
|
|
1275
|
+
rest.delete = async (url, options)=>{
|
|
1276
|
+
camelizer(await rest.makeRequest('DELETE', url, options));
|
|
1277
|
+
};
|
|
1278
|
+
rest.patch = async (url, options)=>{
|
|
1279
|
+
return camelizer(await rest.makeRequest('PATCH', url, options));
|
|
1280
|
+
};
|
|
1281
|
+
rest.put = async (url, options)=>{
|
|
1282
|
+
return camelizer(await rest.makeRequest('PUT', url, options));
|
|
1283
|
+
};
|
|
1284
|
+
return rest;
|
|
1091
1285
|
}
|
|
1092
1286
|
};
|
|
1093
1287
|
return rest;
|