snowtransfer 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/Constants.d.ts +59 -0
  3. package/dist/Constants.js +123 -0
  4. package/dist/Endpoints.d.ts +120 -0
  5. package/dist/Endpoints.js +121 -0
  6. package/dist/RequestHandler.d.ts +258 -0
  7. package/dist/RequestHandler.js +629 -0
  8. package/dist/SnowTransfer.d.ts +70 -0
  9. package/dist/SnowTransfer.js +105 -0
  10. package/dist/StateMachine.d.ts +104 -0
  11. package/dist/StateMachine.js +238 -0
  12. package/dist/StateMachineGraph.d.ts +3 -0
  13. package/dist/StateMachineGraph.js +23 -0
  14. package/dist/Types.d.ts +76 -0
  15. package/dist/Types.js +2 -0
  16. package/dist/index.d.ts +25 -718
  17. package/dist/index.js +63 -44
  18. package/dist/methods/Assets.d.ts +290 -0
  19. package/dist/methods/Assets.js +326 -0
  20. package/dist/methods/AuditLog.d.ts +40 -0
  21. package/dist/methods/AuditLog.js +44 -0
  22. package/dist/methods/AutoModeration.d.ts +122 -0
  23. package/dist/methods/AutoModeration.js +135 -0
  24. package/dist/methods/Bot.d.ts +65 -0
  25. package/dist/methods/Bot.js +75 -0
  26. package/dist/methods/Channel.d.ts +866 -0
  27. package/dist/methods/Channel.js +982 -0
  28. package/dist/methods/Entitlements.d.ts +87 -0
  29. package/dist/methods/Entitlements.js +99 -0
  30. package/dist/methods/Guild.d.ts +722 -0
  31. package/dist/methods/Guild.js +785 -0
  32. package/dist/methods/GuildScheduledEvent.d.ts +138 -0
  33. package/dist/methods/GuildScheduledEvent.js +155 -0
  34. package/dist/methods/GuildTemplate.d.ts +110 -0
  35. package/dist/methods/GuildTemplate.js +124 -0
  36. package/dist/methods/Interaction.d.ts +339 -0
  37. package/dist/methods/Interaction.js +359 -0
  38. package/dist/methods/Invite.d.ts +81 -0
  39. package/dist/methods/Invite.js +107 -0
  40. package/dist/methods/Sku.d.ts +58 -0
  41. package/dist/methods/Sku.js +66 -0
  42. package/dist/methods/StageInstance.d.ts +86 -0
  43. package/dist/methods/StageInstance.js +97 -0
  44. package/dist/methods/User.d.ts +167 -0
  45. package/dist/methods/User.js +184 -0
  46. package/dist/methods/Voice.d.ts +44 -0
  47. package/dist/methods/Voice.js +52 -0
  48. package/dist/methods/Webhook.d.ts +265 -0
  49. package/dist/methods/Webhook.js +256 -0
  50. package/dist/tokenless.d.ts +19 -0
  51. package/dist/tokenless.js +31 -0
  52. package/package.json +9 -9
  53. package/dist/index.js.map +0 -1
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ /* eslint-disable no-async-promise-executor */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RequestHandler = exports.Ratelimiter = exports.Bucket = exports.LeakyCounter = exports.IntervalCounter = exports.DiscordAPIError = void 0;
5
+ const fs = require("node:fs");
6
+ const path = require("node:path");
7
+ const node_events_1 = require("node:events");
8
+ const util = require("node:util");
9
+ const nodeCrypto = require("node:crypto");
10
+ const Endpoints = require("./Endpoints");
11
+ const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), { encoding: "utf8" })); // otherwise, the json was included in the build
12
+ const Constants = require("./Constants");
13
+ const SM = require("./StateMachine");
14
+ // const applicationJSONRegex = /application\/json/;
15
+ const routeRegex = /\/([a-z-]+)\/(?:\d+)/g;
16
+ const reactionsRegex = /\/reactions\/[^/]+/g;
17
+ const reactionsUserRegex = /\/reactions\/:id\/[^/]+/g;
18
+ const webhooksRegex = /^\/webhooks\/(\d+)\/[A-Za-z0-9-_]{64,}/;
19
+ const isMessageEndpointRegex = /\/messages\/:id$/;
20
+ const isGuildChannelsRegex = /\/guilds\/\d+\/channels$/;
21
+ const messagesRegex = /\/messages\/\d+$/;
22
+ const disallowedBodyMethods = new Set(["head", "get", "delete"]);
23
+ /**
24
+ * @since 0.3.0
25
+ */
26
+ class DiscordAPIError extends Error {
27
+ method;
28
+ path;
29
+ code;
30
+ httpStatus;
31
+ // @ts-expect-error Is assigned by Object.defineProperties
32
+ request;
33
+ // @ts-expect-error Is assigned by Object.defineProperties
34
+ response;
35
+ constructor(error, request, response) {
36
+ super();
37
+ this.name = "DiscordAPIError";
38
+ this.message = error.message ?? util.inspect(error);
39
+ this.method = request.method;
40
+ this.path = request.endpoint;
41
+ this.code = error.code ?? 4000;
42
+ this.httpStatus = response.status;
43
+ Object.defineProperties(this, {
44
+ request: { enumerable: false, value: request },
45
+ response: { enumerable: false, value: response },
46
+ });
47
+ }
48
+ }
49
+ exports.DiscordAPIError = DiscordAPIError;
50
+ /**
51
+ * @since 0.17.0
52
+ */
53
+ class IntervalCounter {
54
+ limit;
55
+ reset;
56
+ /**
57
+ * Remaining amount of executions during the current timeframe
58
+ */
59
+ remaining;
60
+ firstRequestTime = 0;
61
+ resetAt = null;
62
+ id = new Array(3).fill(0).map(() => String.fromCodePoint(Math.floor(Math.random() * 26 + 65))).join("");
63
+ /**
64
+ * Create a new base bucket
65
+ * @param limit Number of functions that may be executed during the timeframe set in reset
66
+ * @param reset Timeframe in milliseconds until the ratelimit resets after first
67
+ */
68
+ constructor(limit, reset) {
69
+ this.limit = limit;
70
+ this.reset = reset;
71
+ this.remaining = limit;
72
+ }
73
+ checkReset() {
74
+ // Check if the counter has refreshed
75
+ const now = Date.now();
76
+ // If specific resetAt, use it (ignoring preset reset interval)
77
+ if (this.resetAt !== null) {
78
+ if (now > this.resetAt) {
79
+ this.firstRequestTime = 0;
80
+ this.remaining = this.limit;
81
+ this.resetAt = null;
82
+ if (globalThis.snowtransferDebugLogging)
83
+ console.log(`${new Date().toISOString()} [itrv] [${this.id}] informed reset: ${this.remaining}/${this.limit}`);
84
+ }
85
+ }
86
+ // If no specific resetAt, count from first request and reset interval
87
+ else if (now > this.firstRequestTime + this.reset) {
88
+ this.firstRequestTime = 0;
89
+ this.remaining = this.limit;
90
+ if (globalThis.snowtransferDebugLogging)
91
+ console.log(`${new Date().toISOString()} [itrv] [${this.id}] predicted reset: ${this.remaining}/${this.limit}`);
92
+ }
93
+ }
94
+ hasReset() {
95
+ this.checkReset();
96
+ return this.remaining === this.limit;
97
+ }
98
+ canTake() {
99
+ this.checkReset();
100
+ return this.remaining > 0;
101
+ }
102
+ take() {
103
+ this.checkReset();
104
+ if (this.remaining === this.limit)
105
+ this.firstRequestTime = Date.now();
106
+ if (this.remaining <= 0)
107
+ return false;
108
+ this.remaining--;
109
+ return true;
110
+ }
111
+ timeUntilReset() {
112
+ const now = Date.now();
113
+ if (this.resetAt)
114
+ return Math.max(this.resetAt - now + 1, 0);
115
+ else
116
+ return Math.max(this.firstRequestTime + this.reset - Date.now() + 1, 0);
117
+ }
118
+ // update the firstRequestTime to represent the *response* time to avoid getting fucked by slight network latency inconsistencies after a bucket refresh
119
+ responseReceived() {
120
+ if (this.remaining === this.limit - 1 && this.firstRequestTime)
121
+ this.firstRequestTime = Date.now();
122
+ }
123
+ applyCount(limit, remaining, resetAfter) {
124
+ if (limit != null)
125
+ this.limit = limit;
126
+ this.remaining = remaining;
127
+ this.resetAt = Date.now() + resetAfter;
128
+ }
129
+ }
130
+ exports.IntervalCounter = IntervalCounter;
131
+ /**
132
+ * @since 0.17.0
133
+ */
134
+ class LeakyCounter {
135
+ limit;
136
+ /**
137
+ * Remaining amount of executions during the current timeframe
138
+ */
139
+ remaining;
140
+ resetAt = null;
141
+ id = new Array(3).fill(0).map(() => String.fromCodePoint(Math.floor(Math.random() * 26 + 65))).join("");
142
+ /**
143
+ * Create a new base bucket
144
+ * @param limit Number of functions that may be executed until some are reset
145
+ */
146
+ constructor(limit) {
147
+ this.limit = limit;
148
+ this.remaining = limit;
149
+ }
150
+ checkReset() {
151
+ // Check if the counter has refreshed
152
+ const now = Date.now();
153
+ if (this.resetAt && now > this.resetAt) {
154
+ this.remaining = Math.min(this.limit, this.remaining + 1); // only restore one when it resets
155
+ this.resetAt = null;
156
+ if (globalThis.snowtransferDebugLogging)
157
+ console.log(`${new Date().toISOString()} [leak] [${this.id}] restore one: ${this.remaining}/${this.limit}`);
158
+ }
159
+ }
160
+ hasReset() {
161
+ this.checkReset();
162
+ return this.remaining === this.limit;
163
+ }
164
+ canTake() {
165
+ this.checkReset();
166
+ return this.remaining > 0;
167
+ }
168
+ take() {
169
+ this.checkReset();
170
+ if (globalThis.snowtransferDebugLogging)
171
+ console.log(`${new Date().toISOString()} [leak] [${this.id}] took: ${this.remaining - 1} left`);
172
+ if (this.remaining <= 0)
173
+ return false;
174
+ this.remaining--;
175
+ return true;
176
+ }
177
+ timeUntilReset() {
178
+ const now = Date.now();
179
+ if (this.resetAt)
180
+ return Math.max(this.resetAt - now + 1, 0);
181
+ else
182
+ return 0;
183
+ }
184
+ responseReceived() { }
185
+ applyCount(limit, remaining, resetAfter) {
186
+ if (limit != null)
187
+ this.limit = limit;
188
+ this.remaining = remaining;
189
+ this.resetAt = Date.now() + resetAfter;
190
+ if (globalThis.snowtransferDebugLogging)
191
+ console.log(`${new Date().toISOString()} [leak] [${this.id}] applied: ${remaining}/${limit} - wait another ${resetAfter}`);
192
+ }
193
+ }
194
+ exports.LeakyCounter = LeakyCounter;
195
+ /**
196
+ * Bucket used for saving ratelimits
197
+ * @since 0.1.0
198
+ */
199
+ class Bucket {
200
+ /** Tracks the state this bucket is in (blocked, running, waiting, etc) and what operations are allowed. */
201
+ sm = new SM("ready");
202
+ /** Wrapped functions which always resolve (not reject) after the original function has completed and resolved. The original function may manipulate rate limit buckets in that time before it resolves. */
203
+ calls = [];
204
+ /** The backing counters of the bucket that determine how many functions can be consumed in a timeframe. */
205
+ counters = [];
206
+ pauseRequested = false;
207
+ /**
208
+ * Create a new bucket
209
+ */
210
+ constructor(counters) {
211
+ this.counters = counters;
212
+ // nothing to do, nothing waiting for, the next request will be sent straight away
213
+ this.sm.defineState("ready", {
214
+ onEnter: [],
215
+ onLeave: [],
216
+ transitions: new Map([
217
+ ["enqueue", {
218
+ destination: "check"
219
+ }],
220
+ ["pause", {
221
+ destination: "paused"
222
+ }]
223
+ ])
224
+ });
225
+ // trying to send a request, but should first check if there are any requests etc
226
+ this.sm.defineState("check", {
227
+ onEnter: [
228
+ () => {
229
+ if (this.pauseRequested) {
230
+ return this.sm.doTransition("pause");
231
+ }
232
+ if (!this.counters.every(c => c.canTake())) {
233
+ return this.sm.doTransition("cooldown");
234
+ }
235
+ if (this.calls.length) {
236
+ return this.sm.doTransition("run");
237
+ }
238
+ return this.sm.doTransition("empty");
239
+ }
240
+ ],
241
+ onLeave: [],
242
+ transitions: new Map([
243
+ ["pause", {
244
+ destination: "paused"
245
+ }],
246
+ ["cooldown", {
247
+ destination: "cooldown"
248
+ }],
249
+ ["run", {
250
+ destination: "running"
251
+ }],
252
+ ["empty", {
253
+ destination: "ready"
254
+ }]
255
+ ])
256
+ });
257
+ // a request is in progress, can't do anything else until it gets back (the bucket is sequential)
258
+ this.sm.defineState("running", {
259
+ onEnter: [
260
+ () => {
261
+ // Take one from each counter
262
+ this.counters.forEach(c => c.take());
263
+ // Dequeue and run
264
+ const cb = this.calls.shift();
265
+ cb().then(() => {
266
+ this.sm.doTransition("complete");
267
+ });
268
+ }
269
+ ],
270
+ onLeave: [],
271
+ transitions: new Map([
272
+ ["complete", {
273
+ destination: "check"
274
+ }]
275
+ ])
276
+ });
277
+ // can't send more requests, waiting for rate limit reset. may or may not have pending requests
278
+ this.sm.defineState("cooldown", {
279
+ onEnter: [
280
+ () => {
281
+ const blocked = this.counters.filter(c => !c.canTake());
282
+ this.sm.doTransitionLater("reset", Math.max(...blocked.map(c => c.timeUntilReset()), 1));
283
+ }
284
+ ],
285
+ onLeave: [],
286
+ transitions: new Map([
287
+ ["reset", {
288
+ destination: "check"
289
+ }]
290
+ ])
291
+ });
292
+ // user has requested nothing to be sent for now
293
+ this.sm.defineState("paused", {
294
+ onEnter: [],
295
+ onLeave: [
296
+ () => {
297
+ this.pauseRequested = false;
298
+ }
299
+ ],
300
+ transitions: new Map([
301
+ ["resume", {
302
+ destination: "check"
303
+ }]
304
+ ])
305
+ });
306
+ this.sm.freeze();
307
+ }
308
+ /**
309
+ * Queue a function to be executed
310
+ * @since 0.12.0
311
+ * @param fn function to be executed
312
+ * @returns Result of the function if any
313
+ */
314
+ enqueue(fn) {
315
+ return new Promise((resolve, reject) => {
316
+ this.calls.push(() => {
317
+ return fn(this).then(resolve).catch(reject);
318
+ });
319
+ if (this.sm.currentStateName === "ready")
320
+ this.sm.doTransition("enqueue");
321
+ });
322
+ }
323
+ /**
324
+ * Pause the bucket from consuming more functions until resumed
325
+ * @since 0.16.0
326
+ */
327
+ pause() {
328
+ this.pauseRequested = true;
329
+ if (this.sm.currentStateName === "ready")
330
+ this.sm.doTransition("pause");
331
+ }
332
+ /**
333
+ * If the bucket is paused, resume it
334
+ * @since 0.16.0
335
+ */
336
+ resume() {
337
+ this.pauseRequested = false;
338
+ if (this.sm.currentStateName !== "paused")
339
+ return;
340
+ this.sm.doTransition("resume");
341
+ }
342
+ /**
343
+ * Clear the current queue of events to be sent
344
+ * @since 0.1.0
345
+ */
346
+ dropQueue() {
347
+ this.calls.length = 0;
348
+ }
349
+ }
350
+ exports.Bucket = Bucket;
351
+ /**
352
+ * Ratelimiter used for handling the ratelimits imposed by the rest api
353
+ * @since 0.1.0
354
+ */
355
+ class Ratelimiter {
356
+ /**
357
+ * A Map of Buckets keyed by route keys that store rate limit info
358
+ */
359
+ buckets = new Map();
360
+ /**
361
+ * The bucket that limits how many requests per second you can make globally
362
+ */
363
+ globalBucket = new Bucket([new IntervalCounter(Constants.GLOBAL_REQUESTS_PER_SECOND, 1000)]);
364
+ /**
365
+ * If you're being globally rate limited
366
+ */
367
+ get global() {
368
+ return !this.globalBucket.counters[0].canTake();
369
+ }
370
+ constructor() {
371
+ setInterval(() => {
372
+ for (const [key, value] of this.buckets.entries()) {
373
+ const counter = value.counters[0];
374
+ if (value.sm.currentStateName === "ready" && value.calls.length === 0 && counter.hasReset())
375
+ this.buckets.delete(key);
376
+ }
377
+ }, 1 * 60 * 60 * 1000).unref();
378
+ }
379
+ /**
380
+ * Returns a key for saving ratelimits for routes
381
+ * (Taken from https://github.com/abalabahaha/eris/blob/master/lib/rest/RequestHandler.js) -> I luv u abal <3
382
+ * @since 0.1.0
383
+ * @param url url to reduce to a key something like /channels/266277541646434305/messages/266277541646434305/
384
+ * @param method method of the request, usual http methods like get, etc.
385
+ * @returns reduced url: /channels/266277541646434305/messages/:id/
386
+ */
387
+ routify(url, method) {
388
+ let route = url.replaceAll(routeRegex, function (match, p) {
389
+ return p === "channels" || p === "guilds" || p === "webhooks" ? match : `/${p}/:id`;
390
+ }).replaceAll(reactionsRegex, "/reactions/:id").replaceAll(reactionsUserRegex, "/reactions/:id/:userId").replace(webhooksRegex, "/webhooks/$1/:token");
391
+ if (method === "DELETE" && isMessageEndpointRegex.test(route))
392
+ route = method + route;
393
+ else if (method === "GET" && isGuildChannelsRegex.test(route))
394
+ route = "/guilds/:id/channels";
395
+ if (method === "PUT" || method === "DELETE") {
396
+ const index = route.indexOf("/reactions");
397
+ if (index !== -1)
398
+ route = "MODIFY" + route.slice(0, index + 10);
399
+ }
400
+ return route;
401
+ }
402
+ /**
403
+ * Choose a bucket from the route and enqueue a rest call in it
404
+ * @since 0.1.0
405
+ * @param fn function to call once the ratelimit is ready
406
+ * @param url Endpoint of the request
407
+ * @param method Http method used by the request
408
+ */
409
+ queue(fn, url, method) {
410
+ const routeKey = this.routify(url, method);
411
+ let bucket = this.buckets.get(routeKey);
412
+ if (!bucket) {
413
+ if (method === "DELETE" && messagesRegex.test(url)) {
414
+ bucket = new Bucket([new LeakyCounter(1), new IntervalCounter(5, 5000), this.globalBucket.counters[0]]);
415
+ }
416
+ else
417
+ bucket = new Bucket([new LeakyCounter(1), this.globalBucket.counters[0]]);
418
+ this.buckets.set(routeKey, bucket);
419
+ }
420
+ return bucket.enqueue(fn);
421
+ }
422
+ /**
423
+ * Set if this Ratelimiter is hitting a global ratelimit for `ms` duration
424
+ * @since 0.12.0
425
+ * @param ms How long in milliseconds this Ratelimiter is globally ratelimited for
426
+ */
427
+ setGlobal(ms) {
428
+ this.globalBucket.counters[0].applyCount(Constants.GLOBAL_REQUESTS_PER_SECOND, 0, ms);
429
+ }
430
+ }
431
+ exports.Ratelimiter = Ratelimiter;
432
+ /**
433
+ * Request Handler class
434
+ * @since 0.1.0
435
+ */
436
+ class RequestHandler extends node_events_1.EventEmitter {
437
+ ratelimiter;
438
+ options;
439
+ latency;
440
+ apiURL;
441
+ /**
442
+ * Create a new request handler
443
+ * @param ratelimiter ratelimiter to use for ratelimiting requests
444
+ * @param options options
445
+ */
446
+ constructor(ratelimiter, options) {
447
+ super();
448
+ this.ratelimiter = ratelimiter;
449
+ this.options = {
450
+ baseHost: options?.baseHost ?? Endpoints.BASE_HOST,
451
+ baseURL: options?.baseURL ?? Endpoints.BASE_URL,
452
+ bypassBuckets: options?.bypassBuckets ?? false,
453
+ retryFailed: options?.retryFailed ?? false,
454
+ retryLimit: options?.retryLimit ?? Constants.DEFAULT_RETRY_LIMIT,
455
+ headers: {
456
+ "User-Agent": `Discordbot (https://github.com/DasWolke/SnowTransfer, ${version}) Node.js/${process.version}`
457
+ },
458
+ fetch: options?.fetch ?? fetch
459
+ };
460
+ if (options?.token)
461
+ this.options.headers.Authorization = options.token;
462
+ this.apiURL = this.options.baseHost + this.options.baseURL;
463
+ this.latency = 500;
464
+ }
465
+ request(endpoint, params = {}, method, dataType, data, extraHeaders, retries = this.options.retryLimit, rawResponse = false) {
466
+ const stack = new Error("SnowTransfer dummy Error to capture before processTicksAndRejections").stack;
467
+ return new Promise(async (resolve, reject) => {
468
+ const fn = async (bkt) => {
469
+ const reqId = nodeCrypto.randomBytes(20).toString("hex");
470
+ try {
471
+ const request = { endpoint, method: method.toUpperCase(), dataType, data: data ?? {} };
472
+ this.emit("request", reqId, request);
473
+ const before = Date.now();
474
+ let response;
475
+ switch (dataType) {
476
+ case "json":
477
+ response = await this._request(endpoint, params, method, data, extraHeaders);
478
+ break;
479
+ case "multipart":
480
+ if (!data)
481
+ throw new Error("No multipart data");
482
+ response = await this._multiPartRequest(endpoint, params, method, data, extraHeaders);
483
+ break;
484
+ default:
485
+ throw new Error("Forbidden dataType. Use json or multipart or ensure multipart has FormData");
486
+ }
487
+ this.latency = Date.now() - before;
488
+ bkt?.counters.forEach(c => c.responseReceived());
489
+ if (bkt)
490
+ this._applyRatelimitHeaders(bkt, response.headers);
491
+ if (response.status && !Constants.OK_STATUS_CODES.has(response.status) && response.status !== 429) {
492
+ if (this.options.retryFailed && !Constants.DO_NOT_RETRY_STATUS_CODES.has(response.status) && retries !== 0)
493
+ return resolve(this.request(endpoint, params, method, dataType, data, extraHeaders, retries - 1, rawResponse ? true : undefined));
494
+ throw new DiscordAPIError(parseErrorBody(await response.text()), request, response);
495
+ }
496
+ if (response.status === 429) {
497
+ const b = await response.json(); // Discord says it will be a JSON, so if there's an error, sucks
498
+ if (b.global)
499
+ this.ratelimiter.setGlobal(b.retry_after * 1000);
500
+ if (globalThis.snowtransferDebugLogging)
501
+ console.log(`${new Date().toISOString()} [rate] [${bkt?.counters[0].id}] !! 429 - guess there was 0 remaining, wait another ${b.retry_after * 1000} (route: ${this.ratelimiter.routify(endpoint, method.toUpperCase())})`);
502
+ this.emit("rateLimit", {
503
+ method: method.toUpperCase(),
504
+ path: endpoint,
505
+ route: this.ratelimiter.routify(endpoint, method.toUpperCase())
506
+ });
507
+ if (this.options.retryFailed && retries !== 0)
508
+ return resolve(this.request(endpoint, params, method, dataType, data, extraHeaders, 0, rawResponse ? true : undefined));
509
+ throw new DiscordAPIError({ message: b.message, code: b.code ?? 429 }, request, response);
510
+ }
511
+ this.emit("done", reqId, response, request);
512
+ if (rawResponse)
513
+ return resolve(response);
514
+ if (response.body) {
515
+ let b;
516
+ try {
517
+ b = await response.json();
518
+ }
519
+ catch {
520
+ return resolve(undefined);
521
+ }
522
+ return resolve(b);
523
+ }
524
+ else
525
+ return resolve(undefined);
526
+ }
527
+ catch (error) {
528
+ if (error?.stack)
529
+ error.stack = error.stack + `\n${stack.split("\n").slice(1).join("\n")}`;
530
+ this.emit("requestError", reqId, error);
531
+ return reject(error);
532
+ }
533
+ };
534
+ if (this.options.bypassBuckets)
535
+ fn();
536
+ else
537
+ this.ratelimiter.queue(fn, endpoint, method.toUpperCase());
538
+ });
539
+ }
540
+ /**
541
+ * Apply the received ratelimit headers to the ratelimit bucket
542
+ * @since 0.1.0
543
+ * @param bkt Ratelimit bucket to apply the headers to
544
+ * @param headers Http headers received from discord
545
+ */
546
+ _applyRatelimitHeaders(bkt, headers) {
547
+ const remaining = headers.get("x-ratelimit-remaining");
548
+ const limit = headers.get("x-ratelimit-limit");
549
+ const resetAfter = headers.get("x-ratelimit-reset-after");
550
+ const isGlobal = headers.get("x-ratelimit-global");
551
+ if (remaining === null && !bkt.counters[0].canTake() && !isGlobal) {
552
+ // have to reset it now, or it'll never reset again
553
+ bkt.counters[0].applyCount(null, 1, 0);
554
+ }
555
+ if (remaining && limit && resetAfter && !isGlobal) {
556
+ bkt.counters[0].applyCount(Number.parseInt(limit), Number.parseInt(remaining), Number.parseFloat(resetAfter) * 1000);
557
+ }
558
+ }
559
+ /**
560
+ * Execute a normal json request
561
+ * @since 0.1.0
562
+ * @param endpoint Endpoint to use
563
+ * @param params URL query parameters to add on to the URL
564
+ * @param data Data to send
565
+ * @returns Result of the request
566
+ */
567
+ async _request(endpoint, params = {}, method, data, extraHeaders) {
568
+ const headers = { ...this.options.headers, ...extraHeaders };
569
+ let body = undefined;
570
+ if (data != null && !disallowedBodyMethods.has(method)) {
571
+ if (typeof data === "object")
572
+ body = JSON.stringify(data);
573
+ else
574
+ body = String(data);
575
+ headers["Content-Type"] = "application/json";
576
+ }
577
+ return this.options.fetch(`${this.apiURL}${endpoint}${appendQuery(params)}`, {
578
+ method: method.toUpperCase(),
579
+ headers,
580
+ body
581
+ });
582
+ }
583
+ /**
584
+ * Execute a multipart/form-data request
585
+ * @since 0.1.0
586
+ * @param endpoint Endpoint to use
587
+ * @param params URL query parameters to add on to the URL
588
+ * @param method Http Method to use
589
+ * @param data data to send
590
+ * @returns Result of the request
591
+ */
592
+ async _multiPartRequest(endpoint, params = {}, method, data, extraHeaders) {
593
+ const headers = { ...this.options.headers, ...extraHeaders };
594
+ return this.options.fetch(`${this.apiURL}${endpoint}${appendQuery(params)}`, {
595
+ method: method.toUpperCase(),
596
+ headers,
597
+ body: data
598
+ });
599
+ }
600
+ }
601
+ exports.RequestHandler = RequestHandler;
602
+ function appendQuery(query) {
603
+ const filtered = {};
604
+ let count = 0;
605
+ for (const [key, value] of Object.entries(query)) {
606
+ if (value != undefined) {
607
+ filtered[key] = value;
608
+ count++;
609
+ }
610
+ }
611
+ return count > 0 ? `?${new URLSearchParams(filtered).toString()}` : "";
612
+ }
613
+ /**
614
+ * Try to pull message/code out of a Discord JSON error body so DiscordAPIError.code is usable.
615
+ * Falls back to the raw text if the body isn't JSON.
616
+ */
617
+ function parseErrorBody(text) {
618
+ try {
619
+ const body = JSON.parse(text);
620
+ let message = body.message ?? text;
621
+ // Keep the detailed validation errors visible like the raw text used to be
622
+ if (body.message && body.errors)
623
+ message = `${body.message}\n${util.inspect(body.errors, { depth: Infinity })}`;
624
+ return { message, code: body.code };
625
+ }
626
+ catch {
627
+ return { message: text };
628
+ }
629
+ }
@@ -0,0 +1,70 @@
1
+ import { Ratelimiter, RequestHandler } from "./RequestHandler";
2
+ import AssetsMethods = require("./methods/Assets");
3
+ import AuditLogMethods = require("./methods/AuditLog");
4
+ import AutoModerationMethods = require("./methods/AutoModeration");
5
+ import BotMethods = require("./methods/Bot");
6
+ import ChannelMethods = require("./methods/Channel");
7
+ import EntitlementMethods = require("./methods/Entitlements");
8
+ import GuildMethods = require("./methods/Guild");
9
+ import GuildScheduledEventMethods = require("./methods/GuildScheduledEvent");
10
+ import GuildTemplateMethods = require("./methods/GuildTemplate");
11
+ import InteractionMethods = require("./methods/Interaction");
12
+ import InviteMethods = require("./methods/Invite");
13
+ import SkuMethods = require("./methods/Sku");
14
+ import StageInstanceMethods = require("./methods/StageInstance");
15
+ import UserMethods = require("./methods/User");
16
+ import VoiceMethods = require("./methods/Voice");
17
+ import WebhookMethods = require("./methods/Webhook");
18
+ import type { SnowTransferOptions } from "./Types";
19
+ /**
20
+ * @since 0.1.0
21
+ */
22
+ declare class SnowTransfer {
23
+ /** Options for this SnowTransfer instance */
24
+ options: SnowTransferOptions;
25
+ /** The access token to use for requests. Can be a bot or bearer token */
26
+ token: string | undefined;
27
+ /** Methods related to channels */
28
+ readonly channel: ChannelMethods;
29
+ /** Helper to execute REST calls */
30
+ readonly requestHandler: RequestHandler;
31
+ /** Methods related to users */
32
+ readonly user: UserMethods;
33
+ /** Methods related to stickers and emojis */
34
+ readonly assets: AssetsMethods;
35
+ /** Methods related to webhooks */
36
+ readonly webhook: WebhookMethods;
37
+ /** Methods related to guilds */
38
+ readonly guild: GuildMethods;
39
+ /** Methods related to guild scheduled events */
40
+ readonly guildScheduledEvent: GuildScheduledEventMethods;
41
+ /** Methods related to guild templates */
42
+ readonly guildTemplate: GuildTemplateMethods;
43
+ /** Methods related to application commands/interactions */
44
+ readonly interaction: InteractionMethods;
45
+ /** Methods related to invites */
46
+ readonly invite: InviteMethods;
47
+ /** Methods related to voice regions */
48
+ readonly voice: VoiceMethods;
49
+ /** Methods related to getting gateway connect info */
50
+ readonly bot: BotMethods;
51
+ /** Methods related to guild audit logs */
52
+ readonly auditLog: AuditLogMethods;
53
+ /** Methods related to guild stage instances */
54
+ readonly stageInstance: StageInstanceMethods;
55
+ /** Methods related to guild auto mod */
56
+ readonly autoMod: AutoModerationMethods;
57
+ /** Methods related to entitlements */
58
+ readonly entitlement: EntitlementMethods;
59
+ /** Methods related to SKUs */
60
+ readonly sku: SkuMethods;
61
+ /** Ratelimiter used for handling the ratelimits imposed by the rest api */
62
+ readonly ratelimiter: Ratelimiter;
63
+ /**
64
+ * Create a new Rest Client
65
+ * @param token Discord Bot token to use
66
+ * @param options options
67
+ */
68
+ constructor(token?: string, options?: Partial<SnowTransferOptions>);
69
+ }
70
+ export = SnowTransfer;