@discordeno/rest 19.0.0-next.af64b18 → 19.0.0-next.b044211

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.
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  /**
3
2
  * A invalid request bucket is used in a similar manner as a leaky bucket but a invalid request bucket can be refilled as needed.
4
3
  * It's purpose is to make sure the bot does not hit the limit to getting a 1 hr ban.
@@ -14,8 +13,8 @@ export interface InvalidRequestBucketOptions {
14
13
  max?: number;
15
14
  /** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */
16
15
  interval?: number;
17
- /** timer to reset to 0 */
18
- timeoutId?: NodeJS.Timeout;
16
+ /** When the timeout for the bucket has started at. */
17
+ resetAt?: number;
19
18
  /** how safe to be from max. Defaults to 1 */
20
19
  safety?: number;
21
20
  /** The request statuses that count as an invalid request. */
@@ -25,19 +24,19 @@ export interface InvalidRequestBucketOptions {
25
24
  }
26
25
  export interface InvalidRequestBucket {
27
26
  /** current invalid amount */
28
- current: number;
27
+ invalidRequests: number;
29
28
  /** max invalid requests allowed until ban. Defaults to 10,000 */
30
29
  max: number;
31
30
  /** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */
32
31
  interval: number;
33
- /** timer to reset to 0 */
34
- timeoutId: NodeJS.Timeout | undefined;
32
+ /** When the timeout for this bucket has started at. */
33
+ resetAt: number | undefined;
35
34
  /** how safe to be from max. Defaults to 1 */
36
35
  safety: number;
37
36
  /** The request statuses that count as an invalid request. */
38
37
  errorStatuses: number[];
39
38
  /** The amount of requests that were requested from this bucket. */
40
- requested: number;
39
+ activeRequests: number;
41
40
  /** The requests that are currently pending. */
42
41
  waiting: Array<(value: void | PromiseLike<void>) => void>;
43
42
  /** Whether or not the waiting queue is already processing. */
@@ -1 +1 @@
1
- {"version":3,"file":"invalidBucket.d.ts","sourceRoot":"","sources":["../src/invalidBucket.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,oBAAoB,CAkFrG;AAED,MAAM,WAAW,2BAA2B;IAC1C,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sGAAsG;IACtG,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,OAAO,CAAA;IAC1B,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,sGAAsG;IACtG,QAAQ,EAAE,MAAM,CAAA;IAChB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAA;IACrC,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,6DAA6D;IAC7D,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,OAAO,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;IACzD,8DAA8D;IAC9D,UAAU,EAAE,OAAO,CAAA;IAEnB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,MAAM,CAAA;IAC7B,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,yCAAyC;IACzC,yBAAyB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9C,uDAAuD;IACvD,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,iIAAiI;IACjI,sBAAsB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,KAAK,IAAI,CAAA;CACrE"}
1
+ {"version":3,"file":"invalidBucket.d.ts","sourceRoot":"","sources":["../src/invalidBucket.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,oBAAoB,CAmFrG;AAED,MAAM,WAAW,2BAA2B;IAC1C,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sGAAsG;IACtG,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,6BAA6B;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,sGAAsG;IACtG,QAAQ,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,6DAA6D;IAC7D,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAA;IACtB,+CAA+C;IAC/C,OAAO,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;IACzD,8DAA8D;IAC9D,UAAU,EAAE,OAAO,CAAA;IAEnB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,MAAM,CAAA;IAC7B,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,yCAAyC;IACzC,yBAAyB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9C,uDAAuD;IACvD,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,iIAAiI;IACjI,sBAAsB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,KAAK,IAAI,CAAA;CACrE"}
@@ -7,21 +7,25 @@ import { delay, logger } from '@discordeno/utils';
7
7
  * @returns RefillingBucket
8
8
  */ export function createInvalidRequestBucket(options) {
9
9
  const bucket = {
10
- current: options.current ?? 0,
10
+ invalidRequests: options.current ?? 0,
11
11
  max: options.max ?? 10000,
12
- interval: options.interval ?? 600000,
13
- timeoutId: options.timeoutId,
12
+ interval: options.interval ?? 600_000,
13
+ resetAt: options.resetAt,
14
14
  safety: options.safety ?? 1,
15
15
  errorStatuses: options.errorStatuses ?? [
16
16
  401,
17
17
  403,
18
18
  429
19
19
  ],
20
- requested: options.requested ?? 0,
20
+ activeRequests: options.requested ?? 0,
21
21
  processing: false,
22
22
  waiting: [],
23
23
  requestsAllowed: function() {
24
- return bucket.max - bucket.current - bucket.requested - bucket.safety;
24
+ if (bucket.resetAt !== undefined && Date.now() >= bucket.resetAt) {
25
+ bucket.invalidRequests = 0;
26
+ bucket.resetAt = Date.now() + bucket.interval;
27
+ }
28
+ return bucket.max - bucket.invalidRequests - bucket.activeRequests - bucket.safety;
25
29
  },
26
30
  isRequestAllowed: function() {
27
31
  return bucket.requestsAllowed() > 0;
@@ -31,7 +35,7 @@ import { delay, logger } from '@discordeno/utils';
31
35
  return await new Promise(async (resolve)=>{
32
36
  // If whatever amount of requests is left is more than the safety margin, allow the request
33
37
  if (bucket.isRequestAllowed()) {
34
- bucket.requested++;
38
+ bucket.activeRequests += 1;
35
39
  resolve();
36
40
  } else {
37
41
  bucket.waiting.push(resolve);
@@ -41,41 +45,35 @@ import { delay, logger } from '@discordeno/utils';
41
45
  },
42
46
  processWaiting: async function() {
43
47
  // If already processing, that loop will handle all waiting requests.
44
- if (bucket.processing) {
45
- return;
46
- }
48
+ if (bucket.processing) return;
47
49
  // Mark as processing so other loops don't start
48
50
  bucket.processing = true;
49
51
  while(bucket.waiting.length > 0){
50
- logger.info(`[InvalidBucket] processing waiting queue while loop ran with ${bucket.waiting.length} remaining.`);
51
- if (bucket.isRequestAllowed()) {
52
- bucket.requested++;
53
- // Resolve the next item in the queue
54
- bucket.waiting.shift()?.();
55
- } else {
56
- await delay(1000);
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)}`);
55
+ await delay(bucket.resetAt - Date.now());
57
56
  }
57
+ bucket.activeRequests += 1;
58
+ // Resolve the next item in the queue
59
+ bucket.waiting.shift()?.();
58
60
  }
59
61
  // Mark as false so next pending request can be triggered by new loop.
60
62
  bucket.processing = false;
61
63
  },
62
64
  handleCompletedRequest: function(code, sharedScope) {
63
65
  // Since request is complete, we can remove one from requested.
64
- bucket.requested--;
66
+ bucket.activeRequests -= 1;
65
67
  // Since it is as a valid request, we don't need to do anything
66
68
  if (!bucket.errorStatuses.includes(code)) return;
67
69
  // Shared scope is not considered invalid
68
70
  if (code === 429 && sharedScope) return;
69
71
  // INVALID REQUEST WAS MADE
70
- // Mark a request has been invalid
71
- bucket.current++;
72
- // If a timeout was not started, start a timeout to reset this bucket
73
- if (bucket.timeoutId === undefined) {
74
- bucket.timeoutId = setTimeout(()=>{
75
- bucket.current = 0;
76
- bucket.timeoutId = undefined;
77
- }, bucket.interval);
72
+ if (bucket.resetAt === undefined) {
73
+ bucket.resetAt = Date.now() + bucket.interval;
78
74
  }
75
+ bucket.invalidRequests += 1;
76
+ logger.warn(`[InvalidBucket] an invalid request was made. Increasing invalidRequests count to ${bucket.invalidRequests}`);
79
77
  }
80
78
  };
81
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 current: options.current ?? 0,\n max: options.max ?? 10000,\n interval: options.interval ?? 600000,\n timeoutId: options.timeoutId,\n safety: options.safety ?? 1,\n errorStatuses: options.errorStatuses ?? [401, 403, 429],\n requested: options.requested ?? 0,\n processing: false,\n\n waiting: [],\n\n requestsAllowed: function () {\n return bucket.max - bucket.current - bucket.requested - 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.requested++\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) {\n return\n }\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} remaining.`)\n if (bucket.isRequestAllowed()) {\n bucket.requested++\n // Resolve the next item in the queue\n bucket.waiting.shift()?.()\n } else {\n await delay(1000)\n }\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.requested--\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\n // Mark a request has been invalid\n bucket.current++\n // If a timeout was not started, start a timeout to reset this bucket\n if (bucket.timeoutId === undefined) {\n bucket.timeoutId = setTimeout(() => {\n bucket.current = 0\n bucket.timeoutId = undefined\n }, bucket.interval)\n }\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 /** timer to reset to 0 */\n timeoutId?: NodeJS.Timeout\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 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 /** timer to reset to 0 */\n timeoutId: NodeJS.Timeout | 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 requested: 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","current","max","interval","timeoutId","safety","errorStatuses","requested","processing","waiting","requestsAllowed","isRequestAllowed","waitUntilRequestAvailable","Promise","resolve","push","processWaiting","length","info","shift","handleCompletedRequest","code","sharedScope","includes","undefined","setTimeout"],"mappings":"AAAA,SAASA,KAAK,EAAEC,MAAM,QAAQ,oBAAmB;AAEjD;;;;;;CAMC,GACD,OAAO,SAASC,2BAA2BC,OAAoC,EAAwB;IACrG,MAAMC,SAA+B;QACnCC,SAASF,QAAQE,OAAO,IAAI;QAC5BC,KAAKH,QAAQG,GAAG,IAAI;QACpBC,UAAUJ,QAAQI,QAAQ,IAAI;QAC9BC,WAAWL,QAAQK,SAAS;QAC5BC,QAAQN,QAAQM,MAAM,IAAI;QAC1BC,eAAeP,QAAQO,aAAa,IAAI;YAAC;YAAK;YAAK;SAAI;QACvDC,WAAWR,QAAQQ,SAAS,IAAI;QAChCC,YAAY,KAAK;QAEjBC,SAAS,EAAE;QAEXC,iBAAiB,WAAY;YAC3B,OAAOV,OAAOE,GAAG,GAAGF,OAAOC,OAAO,GAAGD,OAAOO,SAAS,GAAGP,OAAOK,MAAM;QACvE;QAEAM,kBAAkB,WAAY;YAC5B,OAAOX,OAAOU,eAAe,KAAK;QACpC;QAEAE,2BAA2B,iBAAkB;YAC3C,qDAAqD;YACrD,OAAO,MAAM,IAAIC,QAAQ,OAAOC,UAAY;gBAC1C,2FAA2F;gBAC3F,IAAId,OAAOW,gBAAgB,IAAI;oBAC7BX,OAAOO,SAAS;oBAChBO;gBACF,OAAO;oBACLd,OAAOS,OAAO,CAACM,IAAI,CAACD;oBACpB,MAAMd,OAAOgB,cAAc;gBAC7B,CAAC;YACH;QACF;QAEAA,gBAAgB,iBAAkB;YAChC,qEAAqE;YACrE,IAAIhB,OAAOQ,UAAU,EAAE;gBACrB;YACF,CAAC;YAED,gDAAgD;YAChDR,OAAOQ,UAAU,GAAG,IAAI;YAExB,MAAOR,OAAOS,OAAO,CAACQ,MAAM,GAAG,EAAG;gBAChCpB,OAAOqB,IAAI,CAAC,CAAC,6DAA6D,EAAElB,OAAOS,OAAO,CAACQ,MAAM,CAAC,WAAW,CAAC;gBAC9G,IAAIjB,OAAOW,gBAAgB,IAAI;oBAC7BX,OAAOO,SAAS;oBAChB,qCAAqC;oBACrCP,OAAOS,OAAO,CAACU,KAAK;gBACtB,OAAO;oBACL,MAAMvB,MAAM;gBACd,CAAC;YACH;YAEA,sEAAsE;YACtEI,OAAOQ,UAAU,GAAG,KAAK;QAC3B;QAEAY,wBAAwB,SAAUC,IAAI,EAAEC,WAAW,EAAE;YACnD,+DAA+D;YAC/DtB,OAAOO,SAAS;YAChB,+DAA+D;YAC/D,IAAI,CAACP,OAAOM,aAAa,CAACiB,QAAQ,CAACF,OAAO;YAC1C,yCAAyC;YACzC,IAAIA,SAAS,OAAOC,aAAa;YAEjC,2BAA2B;YAE3B,kCAAkC;YAClCtB,OAAOC,OAAO;YACd,qEAAqE;YACrE,IAAID,OAAOI,SAAS,KAAKoB,WAAW;gBAClCxB,OAAOI,SAAS,GAAGqB,WAAW,IAAM;oBAClCzB,OAAOC,OAAO,GAAG;oBACjBD,OAAOI,SAAS,GAAGoB;gBACrB,GAAGxB,OAAOG,QAAQ;YACpB,CAAC;QACH;IACF;IAEA,OAAOH;AACT,CAAC"}
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 CHANGED
@@ -1,3 +1,12 @@
1
1
  import type { CreateRestManagerOptions, RestManager } from './types.js';
2
+ export declare const DISCORD_API_VERSION = 10;
3
+ export declare const DISCORD_API_URL = "https://discord.com/api";
4
+ export declare const AUDIT_LOG_REASON_HEADER = "x-audit-log-reason";
5
+ export declare const RATE_LIMIT_REMAINING_HEADER = "x-ratelimit-remaining";
6
+ export declare const RATE_LIMIT_RESET_AFTER_HEADER = "x-ratelimit-reset-after";
7
+ export declare const RATE_LIMIT_GLOBAL_HEADER = "x-ratelimit-global";
8
+ export declare const RATE_LIMIT_BUCKET_HEADER = "x-ratelimit-bucket";
9
+ export declare const RATE_LIMIT_LIMIT_HEADER = "x-ratelimit-limit";
10
+ export declare const RATE_LIMIT_SCOPE_HEADER = "x-ratelimit-scope";
2
11
  export declare function createRestManager(options: CreateRestManagerOptions): RestManager;
3
12
  //# sourceMappingURL=manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAkDA,OAAO,KAAK,EAA4B,wBAAwB,EAAE,WAAW,EAAsB,MAAM,YAAY,CAAA;AAMrH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,WAAW,CAwnChF"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAkDA,OAAO,KAAK,EAA4B,wBAAwB,EAAE,WAAW,EAAsB,MAAM,YAAY,CAAA;AAKrH,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,CAkoChF"}
package/dist/manager.js CHANGED
@@ -1,29 +1,39 @@
1
- /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-const-assign */ import { InteractionResponseTypes } from '@discordeno/types';
2
- import { calculateBits, camelize, camelToSnakeCase, delay, getBotIdFromToken, logger, processReactionString, urlToBase64 } from '@discordeno/utils';
3
- import fetch from 'node-fetch';
1
+ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-const-assign */ import { calculateBits, camelToSnakeCase, camelize, delay, getBotIdFromToken, logger, processReactionString, urlToBase64 } from '@discordeno/utils';
4
2
  import { createInvalidRequestBucket } from './invalidBucket.js';
5
3
  import { Queue } from './queue.js';
4
+ import { InteractionResponseTypes } from '@discordeno/types';
6
5
  import { createRoutes } from './routes.js';
7
6
  // TODO: make dynamic based on package.json file
8
7
  const version = '19.0.0-alpha.1';
8
+ export const DISCORD_API_VERSION = 10;
9
+ export const DISCORD_API_URL = 'https://discord.com/api';
10
+ export const AUDIT_LOG_REASON_HEADER = 'x-audit-log-reason';
11
+ export const RATE_LIMIT_REMAINING_HEADER = 'x-ratelimit-remaining';
12
+ export const RATE_LIMIT_RESET_AFTER_HEADER = 'x-ratelimit-reset-after';
13
+ export const RATE_LIMIT_GLOBAL_HEADER = 'x-ratelimit-global';
14
+ export const RATE_LIMIT_BUCKET_HEADER = 'x-ratelimit-bucket';
15
+ export const RATE_LIMIT_LIMIT_HEADER = 'x-ratelimit-limit';
16
+ export const RATE_LIMIT_SCOPE_HEADER = 'x-ratelimit-scope';
9
17
  export function createRestManager(options) {
10
18
  const applicationId = options.applicationId ? BigInt(options.applicationId) : options.token ? getBotIdFromToken(options.token) : undefined;
11
19
  if (!applicationId) {
12
20
  throw new Error('`applicationId` was not provided and was not able to extract the id from the bots token. Please explicitly pass `applicationId` to the rest manager.');
13
21
  }
22
+ const baseUrl = options.proxy?.baseUrl ?? DISCORD_API_URL;
14
23
  const rest = {
15
- token: options.token,
16
24
  applicationId,
17
- version: options.version ?? 10,
18
- baseUrl: options.proxy?.baseUrl ?? 'https://discord.com/api',
19
- maxRetryCount: Infinity,
25
+ authorization: options.proxy?.authorization,
26
+ baseUrl,
27
+ deleteQueueDelay: 60000,
20
28
  globallyRateLimited: false,
29
+ invalidBucket: createInvalidRequestBucket({}),
30
+ isProxied: !baseUrl.startsWith(DISCORD_API_URL),
31
+ maxRetryCount: Infinity,
21
32
  processingRateLimitedPaths: false,
22
- deleteQueueDelay: 60000,
23
33
  queues: new Map(),
24
34
  rateLimitedPaths: new Map(),
25
- invalidBucket: createInvalidRequestBucket({}),
26
- authorization: options.proxy?.authorization,
35
+ token: options.token,
36
+ version: options.version ?? DISCORD_API_VERSION,
27
37
  routes: createRoutes(),
28
38
  checkRateLimits (url) {
29
39
  const ratelimited = rest.rateLimitedPaths.get(url);
@@ -50,11 +60,11 @@ export function createRestManager(options) {
50
60
  'permissions',
51
61
  'allow',
52
62
  'deny'
53
- ].includes(key)) {
63
+ ].includes(key) && obj[key] !== undefined) {
54
64
  newObj[key] = calculateBits(obj[key]);
55
65
  continue;
56
66
  }
57
- if (key === 'defaultMemberPermissions') {
67
+ if (key === 'defaultMemberPermissions' && obj[key] !== undefined) {
58
68
  newObj.default_member_permissions = calculateBits(obj[key]);
59
69
  continue;
60
70
  }
@@ -72,7 +82,7 @@ export function createRestManager(options) {
72
82
  if (options?.unauthorized !== false) headers.authorization = `Bot ${rest.token}`;
73
83
  // IF A REASON IS PROVIDED ENCODE IT IN HEADERS
74
84
  if (options?.reason !== undefined) {
75
- headers['x-audit-log-reason'] = encodeURIComponent(options?.reason);
85
+ headers[AUDIT_LOG_REASON_HEADER] = encodeURIComponent(options?.reason);
76
86
  }
77
87
  let body;
78
88
  // TODO: check if we need to add specific check for GET method
@@ -138,13 +148,13 @@ export function createRestManager(options) {
138
148
  /** Processes the rate limit headers and determines if it needs to be rate limited and returns the bucket id if available */ processHeaders (url, headers) {
139
149
  let rateLimited = false;
140
150
  // GET ALL NECESSARY HEADERS
141
- const remaining = headers.get('x-ratelimit-remaining');
142
- const retryAfter = headers.get('x-ratelimit-reset-after');
151
+ const remaining = headers.get(RATE_LIMIT_REMAINING_HEADER);
152
+ const retryAfter = headers.get(RATE_LIMIT_RESET_AFTER_HEADER);
143
153
  const reset = Date.now() + Number(retryAfter) * 1000;
144
- const global = headers.get('x-ratelimit-global');
154
+ const global = headers.get(RATE_LIMIT_GLOBAL_HEADER);
145
155
  // undefined override null needed for typings
146
- const bucketId = headers.get('x-ratelimit-bucket') ?? undefined;
147
- const limit = headers.get('x-ratelimit-limit');
156
+ const bucketId = headers.get(RATE_LIMIT_BUCKET_HEADER) ?? undefined;
157
+ const limit = headers.get(RATE_LIMIT_LIMIT_HEADER);
148
158
  rest.queues.get(url)?.handleCompletedRequest({
149
159
  remaining: remaining ? Number(remaining) : undefined,
150
160
  interval: retryAfter ? Number(retryAfter) * 1000 : undefined,
@@ -199,7 +209,7 @@ export function createRestManager(options) {
199
209
  return rateLimited ? bucketId : undefined;
200
210
  },
201
211
  async sendRequest (options) {
202
- const url = options.url.startsWith('https://') ? options.url : `${rest.baseUrl}/v${rest.version}${options.url}`;
212
+ const url = `${rest.baseUrl}/v${rest.version}${options.route}`;
203
213
  const payload = rest.createRequestBody(options.method, options.requestBodyOptions);
204
214
  logger.debug(`sending request to ${url}`, 'with payload:', {
205
215
  ...payload,
@@ -208,49 +218,57 @@ export function createRestManager(options) {
208
218
  authorization: 'Bot tokenhere'
209
219
  }
210
220
  });
211
- const response = await fetch(url, payload);
221
+ const response = await fetch(url, payload).catch(async (error)=>{
222
+ logger.error(error);
223
+ // Mark request and completed
224
+ rest.invalidBucket.handleCompletedRequest(999, false);
225
+ options.reject({
226
+ ok: false,
227
+ status: 999,
228
+ error: 'Possible network or request shape issue occurred. If this is rare, its a network glitch. If it occurs a lot something is wrong.'
229
+ });
230
+ throw error;
231
+ });
212
232
  logger.debug(`request fetched from ${url} with status ${response.status} & ${response.statusText}`);
233
+ // Mark request and completed
234
+ rest.invalidBucket.handleCompletedRequest(response.status, response.headers.get(RATE_LIMIT_SCOPE_HEADER) === 'shared');
213
235
  // Set the bucket id if it was available on the headers
214
- const bucketId = rest.processHeaders(rest.simplifyUrl(options.url, options.method), response.headers);
236
+ const bucketId = rest.processHeaders(rest.simplifyUrl(options.route, options.method), response.headers);
215
237
  if (bucketId) options.bucketId = bucketId;
216
238
  if (response.status < 200 || response.status >= 400) {
217
239
  logger.debug(`Request to ${url} failed.`);
218
- if (response.status === 429) {
219
- logger.debug(`Request to ${url} was ratelimited.`);
220
- // Too many attempts, get rid of request from queue.
221
- if (options.retryCount++ >= rest.maxRetryCount) {
222
- logger.debug(`Request to ${url} exceeded the maximum allowed retries.`, 'with payload:', payload);
223
- // rest.debug(`[REST - RetriesMaxed] ${JSON.stringify(options)}`)
224
- // Remove item from queue to prevent retry
225
- options.reject({
226
- ok: false,
227
- status: response.status,
228
- error: 'The options was rate limited and it maxed out the retries limit.'
229
- });
230
- return;
231
- }
232
- // Rate limited, add back to queue
233
- rest.invalidBucket.handleCompletedRequest(response.status, response.headers.get('X-RateLimit-Scope') === 'shared');
234
- const resetAfter = response.headers.get('x-ratelimit-reset-after');
235
- if (resetAfter) await delay(Number(resetAfter) * 1000);
236
- // process the response to prevent mem leak
237
- await response.json();
238
- return await options.retryRequest?.(options);
240
+ if (response.status !== 429) {
241
+ options.reject({
242
+ ok: false,
243
+ status: response.status,
244
+ body: await response.text()
245
+ });
246
+ return;
239
247
  }
240
- options.reject({
241
- ok: false,
242
- status: response.status,
243
- body: JSON.stringify(await response.json())
244
- });
245
- return;
248
+ logger.debug(`Request to ${url} was ratelimited.`);
249
+ // Too many attempts, get rid of request from queue.
250
+ if (options.retryCount >= rest.maxRetryCount) {
251
+ logger.debug(`Request to ${url} exceeded the maximum allowed retries.`, 'with payload:', payload);
252
+ // rest.debug(`[REST - RetriesMaxed] ${JSON.stringify(options)}`)
253
+ options.reject({
254
+ ok: false,
255
+ status: response.status,
256
+ error: 'The request was rate limited and it maxed out the retries limit.'
257
+ });
258
+ return;
259
+ }
260
+ options.retryCount += 1;
261
+ const resetAfter = response.headers.get(RATE_LIMIT_RESET_AFTER_HEADER);
262
+ if (resetAfter) await delay(Number(resetAfter) * 1000);
263
+ // process the response to prevent mem leak
264
+ await response.arrayBuffer();
265
+ return await options.retryRequest?.(options);
246
266
  }
247
- const is204 = response.status === 204;
248
- const json = is204 ? undefined : await response.json();
249
- // Discord sometimes sends no response with 204 code
267
+ // Discord sometimes sends no response with no content.
250
268
  options.resolve({
251
269
  ok: true,
252
270
  status: response.status,
253
- body: JSON.stringify(json)
271
+ body: response.status === 204 ? undefined : await response.text()
254
272
  });
255
273
  },
256
274
  simplifyUrl (url, method) {
@@ -275,15 +293,7 @@ export function createRestManager(options) {
275
293
  return parts.join('/');
276
294
  },
277
295
  async processRequest (request) {
278
- const route = request.url.substring(request.url.indexOf('api/'));
279
- const parts = route.split('/');
280
- // Remove the api/
281
- parts.shift();
282
- // Removes the /v#/
283
- if (parts[0]?.startsWith('v')) parts.shift();
284
- // Set the full url to discord api in case it was recieved in a proxy rest
285
- request.url = `${rest.baseUrl}/v${rest.version}/${parts.join('/')}`;
286
- const url = rest.simplifyUrl(request.url, request.method);
296
+ const url = rest.simplifyUrl(request.route, request.method);
287
297
  if (request.runThroughQueue === false) {
288
298
  await rest.sendRequest(request);
289
299
  return;
@@ -303,9 +313,14 @@ export function createRestManager(options) {
303
313
  rest.queues.set(url, bucketQueue);
304
314
  }
305
315
  },
306
- async makeRequest (method, url, options) {
307
- if (!rest.baseUrl.startsWith('https://discord.com') && url[0] === '/') {
308
- const result = await fetch(`${rest.baseUrl}${url}`, rest.createRequestBody(method, options));
316
+ async makeRequest (method, route, options) {
317
+ if (rest.isProxied) {
318
+ if (rest.authorization !== undefined) {
319
+ options ??= {};
320
+ options.headers ??= {};
321
+ options.headers.authorization = rest.authorization;
322
+ }
323
+ const result = await fetch(`${rest.baseUrl}/v${rest.version}${route}`, rest.createRequestBody(method, options));
309
324
  if (!result.ok) {
310
325
  const err = await result.json().catch(()=>{});
311
326
  // Legacy Handling to not break old code or when body is missing
@@ -317,7 +332,7 @@ export function createRestManager(options) {
317
332
  // eslint-disable-next-line no-async-promise-executor
318
333
  return await new Promise(async (resolve, reject)=>{
319
334
  const payload = {
320
- url,
335
+ route,
321
336
  method,
322
337
  requestBodyOptions: options,
323
338
  retryCount: 0,
@@ -1086,5 +1101,12 @@ export function createRestManager(options) {
1086
1101
  };
1087
1102
  return rest;
1088
1103
  }
1104
+ var HttpResponseCode;
1105
+ (function(HttpResponseCode) {
1106
+ HttpResponseCode[HttpResponseCode[/** Minimum value of a code in oder to consider that it was successful. */ "Success"] = 200] = "Success";
1107
+ HttpResponseCode[HttpResponseCode[/** Request completed successfully, but Discord returned an empty body. */ "NoContent"] = 204] = "NoContent";
1108
+ HttpResponseCode[HttpResponseCode[/** Minimum value of a code in order to consider that something went wrong. */ "Error"] = 400] = "Error";
1109
+ HttpResponseCode[HttpResponseCode[/** This request got rate limited. */ "TooManyRequests"] = 429] = "TooManyRequests";
1110
+ })(HttpResponseCode || (HttpResponseCode = {}));
1089
1111
 
1090
1112
  //# sourceMappingURL=manager.js.map