@discordeno/rest 19.0.0-next.f8afb94 → 19.0.0-next.f98bb9b
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 +265 -61
- 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 +203 -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,CAq3ChF"}
|
package/dist/manager.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-const-assign */ import {
|
|
1
|
+
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-const-assign */ import { Buffer } from 'node:buffer';
|
|
2
2
|
import { calculateBits, camelize, camelToSnakeCase, delay, getBotIdFromToken, logger, processReactionString, urlToBase64 } from '@discordeno/utils';
|
|
3
|
-
import fetch from 'node-fetch';
|
|
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,14 @@ export function createRestManager(options) {
|
|
|
36
36
|
token: options.token,
|
|
37
37
|
version: options.version ?? DISCORD_API_VERSION,
|
|
38
38
|
routes: createRoutes(),
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
createBaseHeaders () {
|
|
40
|
+
return {
|
|
41
|
+
'user-agent': `DiscordBot (https://github.com/discordeno/discordeno, v${version})`
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
checkRateLimits (url, headers) {
|
|
45
|
+
const authHeader = headers?.authorization ?? '';
|
|
46
|
+
const ratelimited = rest.rateLimitedPaths.get(`${authHeader}${url}`);
|
|
41
47
|
const global = rest.rateLimitedPaths.get('global');
|
|
42
48
|
const now = Date.now();
|
|
43
49
|
if (ratelimited && now < ratelimited.resetTimestamp) {
|
|
@@ -56,20 +62,27 @@ export function createRestManager(options) {
|
|
|
56
62
|
}
|
|
57
63
|
const newObj = {};
|
|
58
64
|
for (const key of Object.keys(obj)){
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const value = obj[key];
|
|
66
|
+
// Some falsy values should be allowed like null or 0
|
|
67
|
+
if (value !== undefined) {
|
|
68
|
+
switch(key){
|
|
69
|
+
case 'permissions':
|
|
70
|
+
case 'allow':
|
|
71
|
+
case 'deny':
|
|
72
|
+
newObj[key] = typeof value === 'string' ? value : calculateBits(value);
|
|
73
|
+
continue;
|
|
74
|
+
case 'defaultMemberPermissions':
|
|
75
|
+
newObj.default_member_permissions = typeof value === 'string' ? value : calculateBits(value);
|
|
76
|
+
continue;
|
|
77
|
+
case 'nameLocalizations':
|
|
78
|
+
newObj.name_localizations = value;
|
|
79
|
+
continue;
|
|
80
|
+
case 'descriptionLocalizations':
|
|
81
|
+
newObj.description_localizations = value;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
67
84
|
}
|
|
68
|
-
|
|
69
|
-
newObj.default_member_permissions = calculateBits(obj[key]);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
newObj[camelToSnakeCase(key)] = rest.changeToDiscordFormat(obj[key]);
|
|
85
|
+
newObj[camelToSnakeCase(key)] = rest.changeToDiscordFormat(value);
|
|
73
86
|
}
|
|
74
87
|
return newObj;
|
|
75
88
|
}
|
|
@@ -77,10 +90,8 @@ export function createRestManager(options) {
|
|
|
77
90
|
return obj;
|
|
78
91
|
},
|
|
79
92
|
createRequestBody (method, options) {
|
|
80
|
-
const headers =
|
|
81
|
-
|
|
82
|
-
};
|
|
83
|
-
if (options?.unauthorized !== false) headers.authorization = `Bot ${rest.token}`;
|
|
93
|
+
const headers = this.createBaseHeaders();
|
|
94
|
+
if (options?.unauthorized !== true) headers.authorization = `Bot ${rest.token}`;
|
|
84
95
|
// IF A REASON IS PROVIDED ENCODE IT IN HEADERS
|
|
85
96
|
if (options?.reason !== undefined) {
|
|
86
97
|
headers[AUDIT_LOG_REASON_HEADER] = encodeURIComponent(options?.reason);
|
|
@@ -94,12 +105,21 @@ export function createRestManager(options) {
|
|
|
94
105
|
for(let i = 0; i < options.files.length; ++i){
|
|
95
106
|
form.append(`file${i}`, options.files[i].blob, options.files[i].name);
|
|
96
107
|
}
|
|
97
|
-
|
|
108
|
+
// Have to use changeToDiscordFormat or else JSON.stringify may throw an error for the presence of BigInt(s) in the json
|
|
109
|
+
form.append('payload_json', JSON.stringify(rest.changeToDiscordFormat({
|
|
98
110
|
...options.body,
|
|
99
111
|
files: undefined
|
|
100
|
-
}));
|
|
112
|
+
})));
|
|
113
|
+
// No need to set the `content-type` header since `fetch` does that automatically for us when we use a `FormData` object.
|
|
101
114
|
body = form;
|
|
102
|
-
|
|
115
|
+
} else if (options?.body && options.headers && options.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
|
116
|
+
// OAuth2 body handling
|
|
117
|
+
const formBody = [];
|
|
118
|
+
const discordBody = rest.changeToDiscordFormat(options.body);
|
|
119
|
+
for(const prop in discordBody){
|
|
120
|
+
formBody.push(`${encodeURIComponent(prop)}=${encodeURIComponent(discordBody[prop])}`);
|
|
121
|
+
}
|
|
122
|
+
body = formBody.join('&');
|
|
103
123
|
} else if (options?.body !== undefined) {
|
|
104
124
|
if (options.body instanceof FormData) {
|
|
105
125
|
body = options.body;
|
|
@@ -146,7 +166,7 @@ export function createRestManager(options) {
|
|
|
146
166
|
}, 1000);
|
|
147
167
|
}
|
|
148
168
|
},
|
|
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) {
|
|
169
|
+
/** 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
170
|
let rateLimited = false;
|
|
151
171
|
// GET ALL NECESSARY HEADERS
|
|
152
172
|
const remaining = headers.get(RATE_LIMIT_REMAINING_HEADER);
|
|
@@ -156,7 +176,7 @@ export function createRestManager(options) {
|
|
|
156
176
|
// undefined override null needed for typings
|
|
157
177
|
const bucketId = headers.get(RATE_LIMIT_BUCKET_HEADER) ?? undefined;
|
|
158
178
|
const limit = headers.get(RATE_LIMIT_LIMIT_HEADER);
|
|
159
|
-
rest.queues.get(url)?.handleCompletedRequest({
|
|
179
|
+
rest.queues.get(`${requestAuthorization}${url}`)?.handleCompletedRequest({
|
|
160
180
|
remaining: remaining ? Number(remaining) : undefined,
|
|
161
181
|
interval: retryAfter ? Number(retryAfter) * 1000 : undefined,
|
|
162
182
|
max: limit ? Number(limit) : undefined
|
|
@@ -165,14 +185,14 @@ export function createRestManager(options) {
|
|
|
165
185
|
if (remaining === '0') {
|
|
166
186
|
rateLimited = true;
|
|
167
187
|
// SAVE THE URL AS LIMITED, IMPORTANT FOR NEW REQUESTS BY USER WITHOUT BUCKET
|
|
168
|
-
rest.rateLimitedPaths.set(url
|
|
188
|
+
rest.rateLimitedPaths.set(`${requestAuthorization}${url}`, {
|
|
169
189
|
url,
|
|
170
190
|
resetTimestamp: reset,
|
|
171
191
|
bucketId
|
|
172
192
|
});
|
|
173
193
|
// SAVE THE BUCKET AS LIMITED SINCE DIFFERENT URLS MAY SHARE A BUCKET
|
|
174
194
|
if (bucketId) {
|
|
175
|
-
rest.rateLimitedPaths.set(bucketId
|
|
195
|
+
rest.rateLimitedPaths.set(`${requestAuthorization}${bucketId}`, {
|
|
176
196
|
url,
|
|
177
197
|
resetTimestamp: reset,
|
|
178
198
|
bucketId
|
|
@@ -181,8 +201,8 @@ export function createRestManager(options) {
|
|
|
181
201
|
}
|
|
182
202
|
// IF THERE IS NO REMAINING GLOBAL LIMIT, MARK IT RATE LIMITED GLOBALLY
|
|
183
203
|
if (global) {
|
|
184
|
-
const retryAfter = headers.get('retry-after');
|
|
185
|
-
const globalReset = Date.now() +
|
|
204
|
+
const retryAfter = Number(headers.get('retry-after')) * 1000;
|
|
205
|
+
const globalReset = Date.now() + retryAfter;
|
|
186
206
|
// rest.debug(
|
|
187
207
|
// `[REST = Globally Rate Limited] URL: ${url} | Global Rest: ${globalReset}`
|
|
188
208
|
// )
|
|
@@ -190,14 +210,14 @@ export function createRestManager(options) {
|
|
|
190
210
|
rateLimited = true;
|
|
191
211
|
setTimeout(()=>{
|
|
192
212
|
rest.globallyRateLimited = false;
|
|
193
|
-
},
|
|
213
|
+
}, retryAfter);
|
|
194
214
|
rest.rateLimitedPaths.set('global', {
|
|
195
215
|
url: 'global',
|
|
196
216
|
resetTimestamp: globalReset,
|
|
197
217
|
bucketId
|
|
198
218
|
});
|
|
199
219
|
if (bucketId) {
|
|
200
|
-
rest.rateLimitedPaths.set(bucketId
|
|
220
|
+
rest.rateLimitedPaths.set(`${requestAuthorization}${bucketId}`, {
|
|
201
221
|
url: 'global',
|
|
202
222
|
resetTimestamp: globalReset,
|
|
203
223
|
bucketId
|
|
@@ -212,17 +232,33 @@ export function createRestManager(options) {
|
|
|
212
232
|
async sendRequest (options) {
|
|
213
233
|
const url = `${rest.baseUrl}/v${rest.version}${options.route}`;
|
|
214
234
|
const payload = rest.createRequestBody(options.method, options.requestBodyOptions);
|
|
235
|
+
const loggingHeaders = {
|
|
236
|
+
...payload.headers
|
|
237
|
+
};
|
|
238
|
+
const authenticationScheme = payload.headers.authorization?.split(' ')[0];
|
|
239
|
+
if (payload.headers.authorization) {
|
|
240
|
+
loggingHeaders.authorization = `${authenticationScheme} tokenhere`;
|
|
241
|
+
}
|
|
215
242
|
logger.debug(`sending request to ${url}`, 'with payload:', {
|
|
216
243
|
...payload,
|
|
217
|
-
headers:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
244
|
+
headers: loggingHeaders
|
|
245
|
+
});
|
|
246
|
+
const response = await fetch(url, payload).catch(async (error)=>{
|
|
247
|
+
logger.error(error);
|
|
248
|
+
// Mark request and completed
|
|
249
|
+
rest.invalidBucket.handleCompletedRequest(999, false);
|
|
250
|
+
options.reject({
|
|
251
|
+
ok: false,
|
|
252
|
+
status: 999,
|
|
253
|
+
error: 'Possible network or request shape issue occurred. If this is rare, its a network glitch. If it occurs a lot something is wrong.'
|
|
254
|
+
});
|
|
255
|
+
throw error;
|
|
221
256
|
});
|
|
222
|
-
const response = await fetch(url, payload);
|
|
223
257
|
logger.debug(`request fetched from ${url} with status ${response.status} & ${response.statusText}`);
|
|
258
|
+
// Mark request and completed
|
|
259
|
+
rest.invalidBucket.handleCompletedRequest(response.status, response.headers.get(RATE_LIMIT_SCOPE_HEADER) === 'shared');
|
|
224
260
|
// Set the bucket id if it was available on the headers
|
|
225
|
-
const bucketId = rest.processHeaders(rest.simplifyUrl(options.route, options.method), response.headers);
|
|
261
|
+
const bucketId = rest.processHeaders(rest.simplifyUrl(options.route, options.method), response.headers, authenticationScheme === 'Bearer' ? payload.headers.authorization : '');
|
|
226
262
|
if (bucketId) options.bucketId = bucketId;
|
|
227
263
|
if (response.status < 200 || response.status >= 400) {
|
|
228
264
|
logger.debug(`Request to ${url} failed.`);
|
|
@@ -247,8 +283,6 @@ export function createRestManager(options) {
|
|
|
247
283
|
return;
|
|
248
284
|
}
|
|
249
285
|
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
286
|
const resetAfter = response.headers.get(RATE_LIMIT_RESET_AFTER_HEADER);
|
|
253
287
|
if (resetAfter) await delay(Number(resetAfter) * 1000);
|
|
254
288
|
// process the response to prevent mem leak
|
|
@@ -289,23 +323,30 @@ export function createRestManager(options) {
|
|
|
289
323
|
await rest.sendRequest(request);
|
|
290
324
|
return;
|
|
291
325
|
}
|
|
292
|
-
const
|
|
326
|
+
const authHeader = request.requestBodyOptions?.headers?.authorization ?? '';
|
|
327
|
+
const queue = rest.queues.get(`${authHeader}${url}`);
|
|
293
328
|
if (queue !== undefined) {
|
|
294
329
|
queue.makeRequest(request);
|
|
295
330
|
} else {
|
|
296
331
|
// CREATES A NEW QUEUE
|
|
297
332
|
const bucketQueue = new Queue(rest, {
|
|
298
333
|
url,
|
|
299
|
-
deleteQueueDelay: rest.deleteQueueDelay
|
|
334
|
+
deleteQueueDelay: rest.deleteQueueDelay,
|
|
335
|
+
authentication: authHeader
|
|
300
336
|
});
|
|
301
337
|
// Add request to queue
|
|
302
338
|
bucketQueue.makeRequest(request);
|
|
303
339
|
// Save queue
|
|
304
|
-
rest.queues.set(url
|
|
340
|
+
rest.queues.set(`${authHeader}${url}`, bucketQueue);
|
|
305
341
|
}
|
|
306
342
|
},
|
|
307
343
|
async makeRequest (method, route, options) {
|
|
308
344
|
if (rest.isProxied) {
|
|
345
|
+
if (rest.authorization !== undefined) {
|
|
346
|
+
options ??= {};
|
|
347
|
+
options.headers ??= {};
|
|
348
|
+
options.headers.authorization = rest.authorization;
|
|
349
|
+
}
|
|
309
350
|
const result = await fetch(`${rest.baseUrl}/v${rest.version}${route}`, rest.createRequestBody(method, options));
|
|
310
351
|
if (!result.ok) {
|
|
311
352
|
const err = await result.json().catch(()=>{});
|
|
@@ -372,6 +413,11 @@ export function createRestManager(options) {
|
|
|
372
413
|
async addThreadMember (channelId, userId) {
|
|
373
414
|
await rest.put(rest.routes.channels.threads.user(channelId, userId));
|
|
374
415
|
},
|
|
416
|
+
async addDmRecipient (channelId, userId, body) {
|
|
417
|
+
await rest.put(rest.routes.channels.dmRecipient(channelId, userId), {
|
|
418
|
+
body
|
|
419
|
+
});
|
|
420
|
+
},
|
|
375
421
|
async createAutomodRule (guildId, body, reason) {
|
|
376
422
|
return await rest.post(rest.routes.guilds.automod.rules(guildId), {
|
|
377
423
|
body,
|
|
@@ -390,20 +436,34 @@ export function createRestManager(options) {
|
|
|
390
436
|
reason
|
|
391
437
|
});
|
|
392
438
|
},
|
|
393
|
-
async createGlobalApplicationCommand (body) {
|
|
394
|
-
|
|
439
|
+
async createGlobalApplicationCommand (body, options) {
|
|
440
|
+
const restOptions = {
|
|
395
441
|
body
|
|
396
|
-
}
|
|
442
|
+
};
|
|
443
|
+
if (options?.bearerToken) {
|
|
444
|
+
restOptions.unauthorized = true;
|
|
445
|
+
restOptions.headers = {
|
|
446
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return await rest.post(rest.routes.interactions.commands.commands(rest.applicationId), restOptions);
|
|
397
450
|
},
|
|
398
451
|
async createGuild (body) {
|
|
399
452
|
return await rest.post(rest.routes.guilds.all(), {
|
|
400
453
|
body
|
|
401
454
|
});
|
|
402
455
|
},
|
|
403
|
-
async createGuildApplicationCommand (body, guildId) {
|
|
404
|
-
|
|
456
|
+
async createGuildApplicationCommand (body, guildId, options) {
|
|
457
|
+
const restOptions = {
|
|
405
458
|
body
|
|
406
|
-
}
|
|
459
|
+
};
|
|
460
|
+
if (options?.bearerToken) {
|
|
461
|
+
restOptions.unauthorized = true;
|
|
462
|
+
restOptions.headers = {
|
|
463
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return await rest.post(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), restOptions);
|
|
407
467
|
},
|
|
408
468
|
async createGuildFromTemplate (templateCode, body) {
|
|
409
469
|
if (body.icon) {
|
|
@@ -596,7 +656,7 @@ export function createRestManager(options) {
|
|
|
596
656
|
},
|
|
597
657
|
async editBotProfile (options) {
|
|
598
658
|
const avatar = options?.botAvatarURL ? await urlToBase64(options?.botAvatarURL) : options?.botAvatarURL;
|
|
599
|
-
return await rest.patch(rest.routes.
|
|
659
|
+
return await rest.patch(rest.routes.currentUser(), {
|
|
600
660
|
body: {
|
|
601
661
|
username: options.username?.trim(),
|
|
602
662
|
avatar
|
|
@@ -670,7 +730,8 @@ export function createRestManager(options) {
|
|
|
670
730
|
},
|
|
671
731
|
async editMessage (channelId, messageId, body) {
|
|
672
732
|
return await rest.patch(rest.routes.channels.message(channelId, messageId), {
|
|
673
|
-
body
|
|
733
|
+
body,
|
|
734
|
+
files: body.files
|
|
674
735
|
});
|
|
675
736
|
},
|
|
676
737
|
async editOriginalInteractionResponse (token, body) {
|
|
@@ -772,14 +833,62 @@ export function createRestManager(options) {
|
|
|
772
833
|
async getActiveThreads (guildId) {
|
|
773
834
|
return await rest.get(rest.routes.channels.threads.active(guildId));
|
|
774
835
|
},
|
|
775
|
-
async getApplicationCommandPermission (guildId, commandId) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
836
|
+
async getApplicationCommandPermission (guildId, commandId, options) {
|
|
837
|
+
const restOptions = {};
|
|
838
|
+
if (options?.accessToken) {
|
|
839
|
+
restOptions.unauthorized = true;
|
|
840
|
+
restOptions.headers = {
|
|
841
|
+
authorization: `Bearer ${options.accessToken}`
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
return await rest.get(rest.routes.interactions.commands.permission(options?.applicationId ?? rest.applicationId, guildId, commandId), restOptions);
|
|
845
|
+
},
|
|
846
|
+
async getApplicationCommandPermissions (guildId, options) {
|
|
847
|
+
const restOptions = {};
|
|
848
|
+
if (options?.accessToken) {
|
|
849
|
+
restOptions.unauthorized = true;
|
|
850
|
+
restOptions.headers = {
|
|
851
|
+
authorization: `Bearer ${options.accessToken}`
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return await rest.get(rest.routes.interactions.commands.permissions(options?.applicationId ?? rest.applicationId, guildId), restOptions);
|
|
780
855
|
},
|
|
781
856
|
async getApplicationInfo () {
|
|
782
|
-
return await rest.get(rest.routes.
|
|
857
|
+
return await rest.get(rest.routes.oauth2.application());
|
|
858
|
+
},
|
|
859
|
+
async getCurrentAuthenticationInfo (token) {
|
|
860
|
+
return await rest.get(rest.routes.oauth2.currentAuthorization(), {
|
|
861
|
+
headers: {
|
|
862
|
+
authorization: `Bearer ${token}`
|
|
863
|
+
},
|
|
864
|
+
unauthorized: true
|
|
865
|
+
});
|
|
866
|
+
},
|
|
867
|
+
async exchangeToken (clientId, clientSecret, body) {
|
|
868
|
+
const basicCredentials = Buffer.from(`${clientId}:${clientSecret}`);
|
|
869
|
+
const restOptions = {
|
|
870
|
+
body,
|
|
871
|
+
headers: {
|
|
872
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
873
|
+
authorization: `Basic ${basicCredentials.toString('base64')}`
|
|
874
|
+
},
|
|
875
|
+
unauthorized: true
|
|
876
|
+
};
|
|
877
|
+
if (body.grantType === 'client_credentials') {
|
|
878
|
+
restOptions.body.scope = body.scope.join(' ');
|
|
879
|
+
}
|
|
880
|
+
return await rest.post(rest.routes.oauth2.tokenExchange(), restOptions);
|
|
881
|
+
},
|
|
882
|
+
async revokeToken (clientId, clientSecret, body) {
|
|
883
|
+
const basicCredentials = Buffer.from(`${clientId}:${clientSecret}`);
|
|
884
|
+
await rest.post(rest.routes.oauth2.tokenRevoke(), {
|
|
885
|
+
body,
|
|
886
|
+
headers: {
|
|
887
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
888
|
+
authorization: `Basic ${basicCredentials.toString('base64')}`
|
|
889
|
+
},
|
|
890
|
+
unauthorized: true
|
|
891
|
+
});
|
|
783
892
|
},
|
|
784
893
|
async getAuditLog (guildId, options) {
|
|
785
894
|
return await rest.get(rest.routes.guilds.auditlogs(guildId, options));
|
|
@@ -818,6 +927,11 @@ export function createRestManager(options) {
|
|
|
818
927
|
}
|
|
819
928
|
});
|
|
820
929
|
},
|
|
930
|
+
async getGroupDmChannel (body) {
|
|
931
|
+
return await rest.post(rest.routes.channels.dm(), {
|
|
932
|
+
body
|
|
933
|
+
});
|
|
934
|
+
},
|
|
821
935
|
async getEmoji (guildId, emojiId) {
|
|
822
936
|
return await rest.get(rest.routes.guilds.emoji(guildId, emojiId));
|
|
823
937
|
},
|
|
@@ -843,6 +957,14 @@ export function createRestManager(options) {
|
|
|
843
957
|
}) {
|
|
844
958
|
return await rest.get(rest.routes.guilds.guild(guildId, options.counts));
|
|
845
959
|
},
|
|
960
|
+
async getGuilds (token, options) {
|
|
961
|
+
return await rest.get(rest.routes.guilds.userGuilds(options), {
|
|
962
|
+
headers: {
|
|
963
|
+
authorization: `Bearer ${token}`
|
|
964
|
+
},
|
|
965
|
+
unauthorized: true
|
|
966
|
+
});
|
|
967
|
+
},
|
|
846
968
|
async getGuildApplicationCommand (commandId, guildId) {
|
|
847
969
|
return await rest.get(rest.routes.interactions.commands.guilds.one(rest.applicationId, guildId, commandId));
|
|
848
970
|
},
|
|
@@ -938,6 +1060,30 @@ export function createRestManager(options) {
|
|
|
938
1060
|
async getUser (id) {
|
|
939
1061
|
return await rest.get(rest.routes.user(id));
|
|
940
1062
|
},
|
|
1063
|
+
async getCurrentUser (token) {
|
|
1064
|
+
return await rest.get(rest.routes.currentUser(), {
|
|
1065
|
+
headers: {
|
|
1066
|
+
authorization: `Bearer ${token}`
|
|
1067
|
+
},
|
|
1068
|
+
unauthorized: true
|
|
1069
|
+
});
|
|
1070
|
+
},
|
|
1071
|
+
async getUserConnections (token) {
|
|
1072
|
+
return await rest.get(rest.routes.oauth2.connections(), {
|
|
1073
|
+
headers: {
|
|
1074
|
+
authorization: `Bearer ${token}`
|
|
1075
|
+
},
|
|
1076
|
+
unauthorized: true
|
|
1077
|
+
});
|
|
1078
|
+
},
|
|
1079
|
+
async getUserApplicationRoleConnection (token, applicationId) {
|
|
1080
|
+
return await rest.get(rest.routes.oauth2.roleConnections(applicationId), {
|
|
1081
|
+
headers: {
|
|
1082
|
+
authorization: `Bearer ${token}`
|
|
1083
|
+
},
|
|
1084
|
+
unauthorized: true
|
|
1085
|
+
});
|
|
1086
|
+
},
|
|
941
1087
|
async getVanityUrl (guildId) {
|
|
942
1088
|
return await rest.get(rest.routes.guilds.vanity(guildId));
|
|
943
1089
|
},
|
|
@@ -982,6 +1128,9 @@ export function createRestManager(options) {
|
|
|
982
1128
|
async removeThreadMember (channelId, userId) {
|
|
983
1129
|
await rest.delete(rest.routes.channels.threads.user(channelId, userId));
|
|
984
1130
|
},
|
|
1131
|
+
async removeDmRecipient (channelId, userId) {
|
|
1132
|
+
await rest.delete(rest.routes.channels.dmRecipient(channelId, userId));
|
|
1133
|
+
},
|
|
985
1134
|
async sendFollowupMessage (token, options) {
|
|
986
1135
|
return await rest.post(rest.routes.webhooks.webhook(rest.applicationId, token), {
|
|
987
1136
|
body: options,
|
|
@@ -1039,6 +1188,14 @@ export function createRestManager(options) {
|
|
|
1039
1188
|
async getMember (guildId, userId) {
|
|
1040
1189
|
return await rest.get(rest.routes.guilds.members.member(guildId, userId));
|
|
1041
1190
|
},
|
|
1191
|
+
async getCurrentMember (guildId, token) {
|
|
1192
|
+
return await rest.get(rest.routes.guilds.members.currentMember(guildId), {
|
|
1193
|
+
headers: {
|
|
1194
|
+
authorization: `Bearer ${token}`
|
|
1195
|
+
},
|
|
1196
|
+
unauthorized: true
|
|
1197
|
+
});
|
|
1198
|
+
},
|
|
1042
1199
|
async getMembers (guildId, options) {
|
|
1043
1200
|
return await rest.get(rest.routes.guilds.members.members(guildId, options));
|
|
1044
1201
|
},
|
|
@@ -1074,15 +1231,62 @@ export function createRestManager(options) {
|
|
|
1074
1231
|
async triggerTypingIndicator (channelId) {
|
|
1075
1232
|
await rest.post(rest.routes.channels.typing(channelId));
|
|
1076
1233
|
},
|
|
1077
|
-
async upsertGlobalApplicationCommands (body) {
|
|
1078
|
-
|
|
1234
|
+
async upsertGlobalApplicationCommands (body, options) {
|
|
1235
|
+
const restOptions = {
|
|
1079
1236
|
body
|
|
1237
|
+
};
|
|
1238
|
+
if (options?.bearerToken) {
|
|
1239
|
+
restOptions.unauthorized = true;
|
|
1240
|
+
restOptions.headers = {
|
|
1241
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
return await rest.put(rest.routes.interactions.commands.commands(rest.applicationId), restOptions);
|
|
1245
|
+
},
|
|
1246
|
+
async upsertGuildApplicationCommands (guildId, body, options) {
|
|
1247
|
+
const restOptions = {
|
|
1248
|
+
body
|
|
1249
|
+
};
|
|
1250
|
+
if (options?.bearerToken) {
|
|
1251
|
+
restOptions.unauthorized = true;
|
|
1252
|
+
restOptions.headers = {
|
|
1253
|
+
authorization: `Bearer ${options.bearerToken}`
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
return await rest.put(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), restOptions);
|
|
1257
|
+
},
|
|
1258
|
+
async editUserApplicationRoleConnection (token, applicationId, body) {
|
|
1259
|
+
return await rest.put(rest.routes.oauth2.roleConnections(applicationId), {
|
|
1260
|
+
body,
|
|
1261
|
+
headers: {
|
|
1262
|
+
authorization: `Bearer ${token}`
|
|
1263
|
+
},
|
|
1264
|
+
unauthorized: true
|
|
1080
1265
|
});
|
|
1081
1266
|
},
|
|
1082
|
-
async
|
|
1083
|
-
return await rest.put(rest.routes.
|
|
1267
|
+
async addGuildMember (guildId, userId, body) {
|
|
1268
|
+
return await rest.put(rest.routes.guilds.members.member(guildId, userId), {
|
|
1084
1269
|
body
|
|
1085
1270
|
});
|
|
1271
|
+
},
|
|
1272
|
+
preferSnakeCase (enabled) {
|
|
1273
|
+
const camelizer = enabled ? (x)=>x : camelize;
|
|
1274
|
+
rest.get = async (url, options)=>{
|
|
1275
|
+
return camelizer(await rest.makeRequest('GET', url, options));
|
|
1276
|
+
};
|
|
1277
|
+
rest.post = async (url, options)=>{
|
|
1278
|
+
return camelizer(await rest.makeRequest('POST', url, options));
|
|
1279
|
+
};
|
|
1280
|
+
rest.delete = async (url, options)=>{
|
|
1281
|
+
camelizer(await rest.makeRequest('DELETE', url, options));
|
|
1282
|
+
};
|
|
1283
|
+
rest.patch = async (url, options)=>{
|
|
1284
|
+
return camelizer(await rest.makeRequest('PATCH', url, options));
|
|
1285
|
+
};
|
|
1286
|
+
rest.put = async (url, options)=>{
|
|
1287
|
+
return camelizer(await rest.makeRequest('PUT', url, options));
|
|
1288
|
+
};
|
|
1289
|
+
return rest;
|
|
1086
1290
|
}
|
|
1087
1291
|
};
|
|
1088
1292
|
return rest;
|