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