lunibee 0.3.3 → 0.4.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 (125) hide show
  1. package/README.md +1 -1
  2. package/dist/builders/commands.d.ts +1 -1
  3. package/dist/builders/components.d.ts +9 -11
  4. package/dist/builders/embed.d.ts +1 -1
  5. package/dist/builders/index.d.ts +1 -1
  6. package/dist/builders/index.js +87 -1298
  7. package/dist/builders/index.js.map +3 -11
  8. package/dist/chunks/index-0wn81eng.js +2492 -0
  9. package/dist/chunks/index-0wn81eng.js.map +17 -0
  10. package/dist/chunks/index-2en489j6.js +40 -0
  11. package/dist/chunks/index-2en489j6.js.map +10 -0
  12. package/dist/chunks/index-3hyps48a.js +203 -0
  13. package/dist/chunks/index-3hyps48a.js.map +11 -0
  14. package/dist/chunks/index-57c5n3ms.js +2274 -0
  15. package/dist/chunks/index-57c5n3ms.js.map +19 -0
  16. package/dist/chunks/index-5yx92e36.js +1170 -0
  17. package/dist/chunks/index-5yx92e36.js.map +14 -0
  18. package/dist/chunks/index-690bp093.js +95 -0
  19. package/dist/chunks/index-690bp093.js.map +10 -0
  20. package/dist/chunks/index-7fp2hvmg.js +2155 -0
  21. package/dist/chunks/index-7fp2hvmg.js.map +24 -0
  22. package/dist/chunks/index-cnmsajvk.js +1119 -0
  23. package/dist/chunks/index-cnmsajvk.js.map +12 -0
  24. package/dist/chunks/index-fdvgk2sw.js +1310 -0
  25. package/dist/chunks/index-fdvgk2sw.js.map +21 -0
  26. package/dist/chunks/index-gyvps8b9.js +214 -0
  27. package/dist/chunks/index-gyvps8b9.js.map +12 -0
  28. package/dist/chunks/index-jzg4bh99.js +1533 -0
  29. package/dist/chunks/index-jzg4bh99.js.map +21 -0
  30. package/dist/chunks/index-mgw2yvrm.js +3547 -0
  31. package/dist/chunks/index-mgw2yvrm.js.map +25 -0
  32. package/dist/chunks/index-yh76b55h.js +3449 -0
  33. package/dist/chunks/index-yh76b55h.js.map +29 -0
  34. package/dist/collection/index.js +9 -1118
  35. package/dist/collection/index.js.map +3 -6
  36. package/dist/core/api.d.ts +3 -3
  37. package/dist/core/channel-create.d.ts +2 -2
  38. package/dist/core/commands.d.ts +3 -3
  39. package/dist/core/events.d.ts +4 -4
  40. package/dist/core/handles.d.ts +4 -4
  41. package/dist/core/index.d.ts +25 -20
  42. package/dist/core/index.js +68 -10130
  43. package/dist/core/index.js.map +3 -65
  44. package/dist/core/permissions.d.ts +1 -1
  45. package/dist/core/redis-cache.d.ts +1 -1
  46. package/dist/formatters/index.js +41 -110
  47. package/dist/formatters/index.js.map +3 -4
  48. package/dist/handlers/index.js +5 -37
  49. package/dist/handlers/index.js.map +3 -4
  50. package/dist/index.d.ts +21 -21
  51. package/dist/index.js +641 -16325
  52. package/dist/index.js.map +3 -92
  53. package/dist/managers/advanced.d.ts +3 -3
  54. package/dist/managers/application.d.ts +2 -2
  55. package/dist/managers/base.d.ts +1 -1
  56. package/dist/managers/emoji.d.ts +2 -2
  57. package/dist/managers/guild-resources.d.ts +6 -6
  58. package/dist/managers/guild.d.ts +3 -3
  59. package/dist/managers/index.d.ts +4 -8
  60. package/dist/managers/index.js +51 -4060
  61. package/dist/managers/index.js.map +3 -29
  62. package/dist/managers/member.d.ts +2 -2
  63. package/dist/managers/message.d.ts +14 -6
  64. package/dist/managers/role.d.ts +2 -2
  65. package/dist/managers/thread.d.ts +3 -3
  66. package/dist/managers/user.d.ts +7 -4
  67. package/dist/rest/index.d.ts +10 -35
  68. package/dist/rest/index.js +57 -2184
  69. package/dist/rest/index.js.map +3 -18
  70. package/dist/rest/limiter.d.ts +9 -1
  71. package/dist/rest/webhook.d.ts +2 -8
  72. package/dist/sharding/bus.d.ts +2 -2
  73. package/dist/sharding/cluster.d.ts +45 -5
  74. package/dist/sharding/context.d.ts +45 -3
  75. package/dist/sharding/gateway-bot.d.ts +2 -1
  76. package/dist/sharding/health.d.ts +1 -1
  77. package/dist/sharding/identify.d.ts +12 -0
  78. package/dist/sharding/index.d.ts +44 -11
  79. package/dist/sharding/index.js +60 -4620
  80. package/dist/sharding/index.js.map +3 -33
  81. package/dist/sharding/supervisor.d.ts +4 -2
  82. package/dist/structures/audit-log.d.ts +1 -1
  83. package/dist/structures/base.d.ts +6 -6
  84. package/dist/structures/channels.d.ts +1 -1
  85. package/dist/structures/embed.d.ts +1 -1
  86. package/dist/structures/index.d.ts +14 -14
  87. package/dist/structures/index.js +109 -2382
  88. package/dist/structures/index.js.map +3 -16
  89. package/dist/structures/interactions.d.ts +13 -9
  90. package/dist/structures/options.d.ts +1 -1
  91. package/dist/structures/permissions.d.ts +2 -2
  92. package/dist/structures/resources.d.ts +10 -10
  93. package/dist/structures/sticker.d.ts +1 -1
  94. package/dist/types/gateway.d.ts +2 -79
  95. package/dist/types/index.d.ts +0 -6
  96. package/dist/types/index.js +41 -272
  97. package/dist/types/index.js.map +3 -6
  98. package/dist/utils/index.js +23 -209
  99. package/dist/utils/index.js.map +3 -5
  100. package/dist/voice/connection.d.ts +200 -0
  101. package/dist/voice/crypto.d.ts +50 -0
  102. package/dist/voice/dave.d.ts +85 -0
  103. package/dist/voice/effects.d.ts +31 -0
  104. package/dist/voice/emitter.d.ts +14 -0
  105. package/dist/voice/gateway.d.ts +59 -0
  106. package/dist/voice/index.d.ts +25 -228
  107. package/dist/voice/index.js +111 -442
  108. package/dist/voice/index.js.map +3 -4
  109. package/dist/voice/libopus.d.ts +88 -0
  110. package/dist/voice/networking.d.ts +80 -0
  111. package/dist/voice/ogg.d.ts +24 -0
  112. package/dist/voice/opus.d.ts +18 -0
  113. package/dist/voice/player.d.ts +79 -0
  114. package/dist/voice/protocol.d.ts +76 -0
  115. package/dist/voice/receiver.d.ts +59 -0
  116. package/dist/voice/resource.d.ts +102 -0
  117. package/dist/voice/udp.d.ts +40 -0
  118. package/dist/ws/close-codes.d.ts +1 -1
  119. package/dist/ws/index.d.ts +25 -4
  120. package/dist/ws/index.js +47 -1336
  121. package/dist/ws/index.js.map +3 -17
  122. package/dist/ws/protocol.d.ts +1 -1
  123. package/dist/ws/send-budget.d.ts +0 -2
  124. package/dist/ws/state.d.ts +0 -10
  125. package/package.json +19 -16
@@ -1,2188 +1,61 @@
1
1
  // @bun
2
- // packages/rest/package.json
3
- var package_default = {
4
- name: "@lunibee/rest",
5
- version: "0.3.3",
6
- type: "module",
7
- module: "./src/index.ts",
8
- types: "./src/index.ts",
9
- exports: {
10
- ".": "./src/index.ts"
11
- },
12
- dependencies: {
13
- "@lunibee/types": "workspace:*"
14
- }
15
- };
16
-
17
- // packages/rest/src/store.ts
18
- var GLOBAL_WINDOW_MS = 1050;
19
- function takeGlobalSlot(log, now, limit, window = GLOBAL_WINDOW_MS) {
20
- while (log.length > 0 && log[0] <= now - window)
21
- log.shift();
22
- if (log.length >= limit)
23
- return Math.max(1, log[0] + window - now + 1);
24
- log.push(now);
25
- return 0;
26
- }
27
- var PROBE_POLL_MS = 50;
28
- var DEFAULT_PROBE_MS = 16000;
29
- var BUCKET_LINGER_MS = 60000;
30
- var WINDOW_TOLERANCE_MS = 50;
31
- function reserveState(state, now, probeMs) {
32
- if (state && state.resetAt > now) {
33
- if (state.remaining > 0)
34
- return {
35
- reservation: {
36
- granted: true,
37
- known: true,
38
- resetAt: state.resetAt
39
- },
40
- next: { ...state, remaining: state.remaining - 1 }
41
- };
42
- const wait = state.resetAt - now;
43
- return {
44
- reservation: {
45
- granted: false,
46
- retryAfterMs: state.provisional ? Math.min(wait, PROBE_POLL_MS) : wait,
47
- reason: state.probe ? "probe" : "bucket"
48
- }
49
- };
50
- }
51
- const resetAt = now + probeMs;
52
- if (state?.limit !== undefined && state.limit > 0)
53
- return {
54
- reservation: { granted: true, known: true, resetAt },
55
- next: {
56
- remaining: state.limit - 1,
57
- resetAt,
58
- limit: state.limit,
59
- provisional: true
60
- }
61
- };
62
- return {
63
- reservation: { granted: true, known: false, resetAt },
64
- next: { remaining: 0, resetAt, provisional: true, probe: true }
65
- };
66
- }
67
- function reconcileState(state, update, now) {
68
- const incoming = update.resetAfterMs === undefined ? undefined : Math.ceil(now + update.resetAfterMs);
69
- const active = !!state && state.resetAt > now && !state.probe;
70
- let next;
71
- if (active) {
72
- next = { ...state };
73
- const stale = update.window !== undefined && next.window !== undefined && update.window < next.window - WINDOW_TOLERANCE_MS;
74
- if (!stale) {
75
- if (update.remaining !== undefined)
76
- next.remaining = Math.min(next.remaining, update.remaining);
77
- if (incoming !== undefined) {
78
- const newer = update.window !== undefined && next.window !== undefined && update.window > next.window + WINDOW_TOLERANCE_MS;
79
- next.resetAt = next.provisional || newer ? incoming : Math.max(next.resetAt, incoming);
80
- delete next.provisional;
81
- }
82
- if (update.window !== undefined)
83
- next.window = Math.max(next.window ?? 0, update.window);
84
- }
85
- } else {
86
- if (update.remaining === undefined && update.retryAfterMs === undefined) {
87
- return state?.probe ? null : undefined;
88
- }
89
- next = {
90
- remaining: update.remaining ?? 0,
91
- resetAt: incoming ?? now
92
- };
93
- if (state?.limit !== undefined)
94
- next.limit = state.limit;
95
- if (update.window !== undefined)
96
- next.window = update.window;
97
- }
98
- if (update.limit !== undefined && update.limit > 0)
99
- next.limit = update.limit;
100
- if (update.retryAfterMs !== undefined) {
101
- next.remaining = 0;
102
- next.resetAt = Math.max(next.resetAt, Math.ceil(now + update.retryAfterMs));
103
- delete next.provisional;
104
- }
105
- return next;
106
- }
107
-
108
- class MemoryRateLimitStore {
109
- #buckets = new Map;
110
- #routeBuckets = new Map;
111
- #globals = new Map;
112
- #globalLogs = new Map;
113
- static #PRUNE_INTERVAL = 60000;
114
- #lastPruneAt = 0;
115
- static #MAX_ENTRIES = 1e4;
116
- reservesGlobal = true;
117
- constructor() {}
118
- static #evict(map) {
119
- while (map.size > MemoryRateLimitStore.#MAX_ENTRIES)
120
- map.delete(map.keys().next().value);
121
- }
122
- getBucketHash(route) {
123
- return this.#routeBuckets.get(route);
124
- }
125
- setBucketHash(route, hash) {
126
- this.#routeBuckets.delete(route);
127
- this.#routeBuckets.set(route, hash);
128
- MemoryRateLimitStore.#evict(this.#routeBuckets);
129
- }
130
- getBucket(key) {
131
- return this.#buckets.get(key);
132
- }
133
- reserve(key, options = {}) {
134
- const now = Date.now();
135
- if (options.global !== undefined) {
136
- const globalWait = this.getGlobalReset(options.global) - now;
137
- if (globalWait > 0)
138
- return {
139
- granted: false,
140
- retryAfterMs: globalWait,
141
- reason: "global"
142
- };
143
- }
144
- const limit = options.global === undefined ? 0 : options.globalLimit ?? 0;
145
- let log;
146
- if (limit > 0) {
147
- log = this.#globalLogs.get(options.global);
148
- if (!log) {
149
- this.#globalLogs.set(options.global, log = []);
150
- MemoryRateLimitStore.#evict(this.#globalLogs);
151
- }
152
- const wait = takeGlobalSlot(log, now, limit);
153
- if (wait > 0)
154
- return { granted: false, retryAfterMs: wait, reason: "global" };
155
- log.pop();
156
- }
157
- const { reservation, next } = reserveState(this.#buckets.get(key), now, options.probeMs ?? DEFAULT_PROBE_MS);
158
- if (log && reservation.granted)
159
- log.push(now);
160
- if (next)
161
- this.#write(key, next);
162
- return reservation;
163
- }
164
- reconcile(key, update) {
165
- const next = reconcileState(this.#buckets.get(key), update, Date.now());
166
- if (next === null)
167
- this.#buckets.delete(key);
168
- else if (next)
169
- this.#write(key, next);
170
- }
171
- release(key, grant, refund) {
172
- const state = this.#buckets.get(key);
173
- if (!state || state.resetAt !== grant.resetAt)
174
- return;
175
- if (state.probe)
176
- this.#buckets.delete(key);
177
- else if (refund)
178
- state.remaining = Math.min(state.remaining + 1, state.limit ?? Number.POSITIVE_INFINITY);
179
- }
180
- updateBucket(key, state) {
181
- this.#write(key, state);
182
- }
183
- #write(key, state) {
184
- this.#buckets.delete(key);
185
- this.#buckets.set(key, state);
186
- MemoryRateLimitStore.#evict(this.#buckets);
187
- const now = Date.now();
188
- if (now - this.#lastPruneAt >= MemoryRateLimitStore.#PRUNE_INTERVAL) {
189
- this.#lastPruneAt = now;
190
- this.prune(BUCKET_LINGER_MS);
191
- }
192
- }
193
- prune(maxAge = 0) {
194
- const cutoff = Date.now() - Math.max(0, maxAge);
195
- for (const [key, state] of this.#buckets) {
196
- if (state.resetAt <= cutoff)
197
- this.#buckets.delete(key);
198
- }
199
- for (const [scope, resetAt] of this.#globals)
200
- if (resetAt <= Date.now())
201
- this.#globals.delete(scope);
202
- for (const [scope, log] of this.#globalLogs)
203
- if ((log.at(-1) ?? 0) <= Date.now() - GLOBAL_WINDOW_MS)
204
- this.#globalLogs.delete(scope);
205
- }
206
- getGlobalReset(scope = "") {
207
- return this.#globals.get(scope) ?? 0;
208
- }
209
- setGlobalReset(resetAt, scope = "") {
210
- this.#globals.set(scope, resetAt);
211
- MemoryRateLimitStore.#evict(this.#globals);
212
- }
213
- extendGlobalReset(scope, waitMs) {
214
- const resetAt = Date.now() + Math.max(0, waitMs);
215
- if (resetAt > this.getGlobalReset(scope))
216
- this.setGlobalReset(resetAt, scope);
217
- }
218
- get size() {
219
- return this.#buckets.size;
220
- }
221
- }
222
-
223
- // packages/rest/src/errors.ts
224
- function kindFromStatus(status) {
225
- if (status === 429)
226
- return "rateLimited";
227
- if (status === 401)
228
- return "authentication";
229
- if (status === 403)
230
- return "permission";
231
- if (status === 404)
232
- return "notFound";
233
- if (status === 400 || status === 422)
234
- return "validation";
235
- if (status >= 500)
236
- return "server";
237
- if (status === 0)
238
- return "network";
239
- return "client";
240
- }
241
- function redactPath(path) {
242
- return path.replace(/^(\/webhooks\/[^/?]+\/)[^/?]+/i, "$1:token").replace(/^(\/interactions\/[^/?]+\/)[^/?]+/i, "$1:token");
243
- }
244
- var TOKEN_IN_TEXT = /(\/(?:webhooks|interactions)\/[^/\s?"'`]+\/)[^/\s?"'`]+/gi;
245
- function redactText(text) {
246
- return text.replace(TOKEN_IN_TEXT, "$1:token");
247
- }
248
- function sanitizeCause(error) {
249
- if (!(error instanceof Error))
250
- return;
251
- const copy = new Error(redactText(error.message));
252
- copy.name = error.name;
253
- if (error.stack)
254
- copy.stack = redactText(error.stack);
255
- for (const key of ["code", "errno", "syscall"]) {
256
- const value = error[key];
257
- if (typeof value === "string" || typeof value === "number")
258
- copy[key] = value;
259
- }
260
- return copy;
261
- }
262
- var HINTS = {
263
- 50001: "The bot cannot see this resource: check that it is in the guild and has View Channel there.",
264
- 50013: "The bot lacks a permission for this action, or the target role or member is at or above the bot's highest role."
265
- };
266
-
267
- class RESTError extends Error {
268
- kind;
269
- status;
270
- code;
271
- errors;
272
- method;
273
- path;
274
- hint;
275
- constructor(message, status, code, errors, options = {}) {
276
- super(message, options.cause === undefined ? undefined : { cause: options.cause });
277
- this.name = "RESTError";
278
- this.status = status;
279
- this.kind = options.kind ?? kindFromStatus(status);
280
- this.code = code;
281
- this.hint = code === undefined ? undefined : HINTS[code];
282
- this.errors = errors;
283
- this.method = options.method;
284
- this.path = options.path === undefined ? undefined : redactPath(options.path);
285
- }
286
- get retryable() {
287
- return this.kind === "rateLimited" || this.kind === "server" || this.kind === "network" || this.kind === "timeout" || this.kind === "coordination" || this.kind === "overloaded";
288
- }
289
- }
290
- function abortError(path, reason) {
291
- return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason, kind: "aborted" });
292
- }
293
- var MAX_TIMER_MS = 2147483647;
294
- async function sleep(delay, signal, path) {
295
- if (delay <= 0)
296
- return;
297
- delay = Math.min(delay, MAX_TIMER_MS);
298
- await new Promise((resolve, reject) => {
299
- const finish = (error) => {
300
- clearTimeout(timer);
301
- signal?.removeEventListener("abort", onAbort);
302
- if (error)
303
- reject(error);
304
- else
305
- resolve();
306
- };
307
- const onAbort = () => finish(abortError(path, signal?.reason));
308
- const timer = setTimeout(finish, delay);
309
- if (signal?.aborted)
310
- return onAbort();
311
- signal?.addEventListener("abort", onAbort, { once: true });
312
- });
313
- }
314
- async function abortable(promise, signal, path) {
315
- if (!signal)
316
- return promise;
317
- if (signal.aborted)
318
- throw abortError(path, signal.reason);
319
- return new Promise((resolve, reject) => {
320
- const onAbort = () => reject(abortError(path, signal.reason));
321
- signal.addEventListener("abort", onAbort, { once: true });
322
- promise.then(() => {
323
- signal.removeEventListener("abort", onAbort);
324
- resolve();
325
- }, (error) => {
326
- signal.removeEventListener("abort", onAbort);
327
- reject(error);
328
- });
329
- });
330
- }
331
-
332
- // packages/rest/src/route.ts
333
- import { createHash } from "crypto";
334
- function normalizeRoutePath(path) {
335
- const queryIndex = path.indexOf("?");
336
- const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
337
- return redactPath(routePath).replace(/\/\d+(?=\/|$)/g, "/:id").replace(/\/reactions\/[^/]+/i, "/reactions/:emoji");
338
- }
339
- function majorParameter(path) {
340
- const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/i.exec(path);
341
- if (!match)
342
- return "@none";
343
- if (match[1].toLowerCase() === "webhooks" && match[3] !== undefined)
344
- return `${match[2]}:${tokenDigest(match[3])}`;
345
- return match[2];
346
- }
347
- function tokenDigest(token) {
348
- return createHash("sha256").update(token).digest("hex").slice(0, 16);
349
- }
350
- function scopeBucket(hashOrRoute, major) {
351
- return `${hashOrRoute}|${major}`;
352
- }
353
- function createRouteKey(method, path) {
354
- const normalizedMethod = method.toUpperCase();
355
- return {
356
- method: normalizedMethod,
357
- path,
358
- route: `${normalizedMethod}:${normalizeRoutePath(path)}`,
359
- major: majorParameter(path)
360
- };
361
- }
362
- function serializeQuery(query) {
363
- if (query === undefined)
364
- return "";
365
- if (typeof query === "string")
366
- return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
367
- const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
368
- const serialized = params.toString();
369
- return serialized.length === 0 ? "" : `?${serialized}`;
370
- }
371
-
372
- // packages/rest/src/scheduler.ts
373
- class RequestScheduler {
374
- static MAX_REMAPS = 2;
375
- #queues = new Map;
376
- #waiting = new Map;
377
- #queued = 0;
378
- #maxQueued;
379
- #maxQueuedPerBucket;
380
- constructor(options = {}) {
381
- this.#maxQueued = options.maxQueued ?? Number.POSITIVE_INFINITY;
382
- this.#maxQueuedPerBucket = options.maxQueuedPerBucket ?? Number.POSITIVE_INFINITY;
383
- if (!(this.#maxQueued >= 0) || !(this.#maxQueuedPerBucket >= 0))
384
- throw new RangeError("Queue limits must be non-negative numbers.");
385
- }
386
- get size() {
387
- return this.#queues.size;
388
- }
389
- get queued() {
390
- return this.#queued;
391
- }
392
- async runResolved(resolveKey, signal, path, task) {
393
- let key = await resolveKey();
394
- for (let hop = 0;hop < RequestScheduler.MAX_REMAPS; hop++) {
395
- const outcome = await this.run(key, signal, path, async (release) => {
396
- const current = await resolveKey();
397
- if (current !== key)
398
- return { done: false, key: current };
399
- return { done: true, value: await task(key, release) };
400
- });
401
- if (outcome.done)
402
- return outcome.value;
403
- key = outcome.key;
404
- }
405
- return this.run(key, signal, path, (release) => task(key, release));
406
- }
407
- async run(key, signal, path, task) {
408
- const previous = this.#queues.get(key);
409
- const waiting = this.#waiting.get(key) ?? 0;
410
- if (previous && (this.#queued >= this.#maxQueued || waiting >= this.#maxQueuedPerBucket))
411
- throw new RESTError("The REST request queue is full; request not sent.", 0, undefined, undefined, { path, kind: "overloaded" });
412
- let open;
413
- const gate = new Promise((resolve) => {
414
- open = resolve;
415
- });
416
- this.#queues.set(key, gate);
417
- let released = false;
418
- const release = () => {
419
- if (released)
420
- return;
421
- released = true;
422
- open();
423
- if (this.#queues.get(key) === gate)
424
- this.#queues.delete(key);
425
- };
426
- try {
427
- if (previous) {
428
- this.#queued++;
429
- this.#waiting.set(key, waiting + 1);
430
- try {
431
- await abortable(previous, signal, path);
432
- } finally {
433
- this.#queued--;
434
- const left = (this.#waiting.get(key) ?? 1) - 1;
435
- if (left > 0)
436
- this.#waiting.set(key, left);
437
- else
438
- this.#waiting.delete(key);
439
- }
440
- } else
441
- await abortable(Promise.resolve(), signal, path);
442
- return await task(release);
443
- } finally {
444
- release();
445
- }
446
- }
447
- }
448
-
449
- // packages/rest/src/headers.ts
450
- function headerNumber(headers, name) {
451
- const raw = headers.get(name);
452
- if (raw === null || raw.trim() === "")
453
- return;
454
- const value = Number(raw);
455
- return Number.isFinite(value) && value >= 0 ? value : undefined;
456
- }
457
- var BUCKET_HASH = /^[A-Za-z0-9:_\-.]{1,64}$/;
458
- var MAX_COUNT = 1e5;
459
- function count(value) {
460
- return value === undefined || value > MAX_COUNT ? undefined : Math.floor(value);
461
- }
462
- function parseRateLimitHeaders(headers) {
463
- const remaining = headerNumber(headers, "X-RateLimit-Remaining");
464
- const limit = headerNumber(headers, "X-RateLimit-Limit");
465
- const resetAfter = headerNumber(headers, "X-RateLimit-Reset-After");
466
- const reset = headerNumber(headers, "X-RateLimit-Reset");
467
- const retryAfter = headerNumber(headers, "Retry-After");
468
- const bucket = headers.get("X-RateLimit-Bucket")?.trim();
469
- const scope = headers.get("X-RateLimit-Scope")?.trim().toLowerCase();
470
- return {
471
- limit: count(limit),
472
- remaining: count(remaining),
473
- resetAfterMs: resetAfter === undefined ? undefined : resetAfter * 1000,
474
- resetAtMs: reset === undefined ? undefined : reset * 1000,
475
- bucket: bucket && BUCKET_HASH.test(bucket) ? bucket : undefined,
476
- scope: scope === "user" || scope === "global" || scope === "shared" ? scope : undefined,
477
- global: headers.get("X-RateLimit-Global")?.trim().toLowerCase() === "true",
478
- retryAfterMs: retryAfter === undefined ? undefined : retryAfter * 1000
479
- };
480
- }
481
-
482
- // packages/rest/src/telemetry.ts
483
- function emitTelemetry(sink, event) {
484
- if (!sink)
485
- return;
486
- try {
487
- sink.emit(event);
488
- } catch {}
489
- }
490
- var LATENCY_BOUNDS = [
491
- 1,
492
- 5,
493
- 10,
494
- 25,
495
- 50,
496
- 100,
497
- 250,
498
- 500,
499
- 1000,
500
- 2500,
501
- 5000,
502
- 1e4,
503
- 30000,
504
- 60000
505
- ];
506
-
507
- class RESTMetrics {
508
- #counters = new Map;
509
- #gauges = new Map;
510
- #histograms = new Map;
511
- #routes = new Set;
512
- #maxRoutes;
513
- constructor(options = {}) {
514
- this.#maxRoutes = options.maxRoutes ?? 500;
515
- }
516
- #route(route) {
517
- if (this.#routes.has(route))
518
- return route;
519
- if (this.#routes.size >= this.#maxRoutes)
520
- return "other";
521
- this.#routes.add(route);
522
- return route;
523
- }
524
- #inc(name, labels = {}, by = 1) {
525
- const key = metricKey(name, labels);
526
- this.#counters.set(key, (this.#counters.get(key) ?? 0) + by);
527
- }
528
- #observe(name, value, labels = {}) {
529
- const key = metricKey(name, labels);
530
- let h = this.#histograms.get(key);
531
- if (!h) {
532
- h = {
533
- counts: new Array(LATENCY_BOUNDS.length + 1).fill(0),
534
- sum: 0,
535
- count: 0
536
- };
537
- this.#histograms.set(key, h);
538
- }
539
- let i = LATENCY_BOUNDS.findIndex((bound) => value <= bound);
540
- if (i === -1)
541
- i = LATENCY_BOUNDS.length;
542
- h.counts[i]++;
543
- h.sum += value;
544
- h.count++;
545
- }
546
- emit(event) {
547
- switch (event.type) {
548
- case "response":
549
- this.#inc("responses_total", {
550
- method: event.method,
551
- route: this.#route(event.route),
552
- status: String(event.status)
553
- });
554
- this.#observe("response_latency_ms", event.durationMs, {
555
- method: event.method
556
- });
557
- break;
558
- case "request":
559
- this.#inc("requests_total", {
560
- method: event.method,
561
- outcome: event.ok ? "ok" : "error"
562
- });
563
- this.#observe("request_latency_ms", event.durationMs, {
564
- method: event.method
565
- });
566
- break;
567
- case "rateLimited":
568
- this.#inc("rate_limited_total", {
569
- scope: event.scope,
570
- route: this.#route(event.route)
571
- });
572
- break;
573
- case "wait":
574
- this.#inc("waits_total", { reason: event.reason });
575
- this.#observe("wait_ms", event.durationMs, {
576
- reason: event.reason
577
- });
578
- break;
579
- case "reservation":
580
- this.#inc("reservations_total", {
581
- outcome: event.granted ? "granted" : "refused"
582
- });
583
- this.#observe("reservation_latency_ms", event.latencyMs);
584
- break;
585
- case "retry":
586
- this.#inc("retries_total", { reason: event.reason });
587
- break;
588
- case "cancelled":
589
- this.#inc("cancellations_total", { stage: event.stage });
590
- break;
591
- case "overloaded":
592
- this.#inc("overloaded_total");
593
- break;
594
- case "tokenRejected":
595
- this.#inc("token_rejected_total");
596
- break;
597
- case "networkError":
598
- this.#inc("network_errors_total", {
599
- timeout: String(event.timedOut)
600
- });
601
- break;
602
- case "bucket":
603
- this.#inc("bucket_changes_total", { change: event.change });
604
- break;
605
- case "store":
606
- this.#inc("store_commands_total", {
607
- operation: event.operation,
608
- ok: String(event.ok)
609
- });
610
- this.#observe("store_latency_ms", event.latencyMs, {
611
- operation: event.operation
612
- });
613
- break;
614
- case "mode":
615
- this.#inc("mode_changes_total", { mode: event.mode });
616
- this.#gauges.set("degraded", event.mode === "degraded-local" || event.mode === "unavailable" ? 1 : 0);
617
- break;
618
- case "queue":
619
- this.#gauges.set("queued", event.queued);
620
- this.#gauges.set("in_flight", event.inFlight);
621
- break;
622
- }
623
- }
624
- snapshot() {
625
- const histograms = {};
626
- for (const [key, h] of this.#histograms) {
627
- const buckets = {};
628
- let running = 0;
629
- LATENCY_BOUNDS.forEach((bound, i) => {
630
- running += h.counts[i];
631
- buckets[String(bound)] = running;
632
- });
633
- buckets["+Inf"] = h.count;
634
- histograms[key] = { count: h.count, sum: h.sum, buckets };
635
- }
636
- return {
637
- counters: Object.fromEntries(this.#counters),
638
- gauges: Object.fromEntries(this.#gauges),
639
- histograms
640
- };
641
- }
642
- reset() {
643
- this.#counters.clear();
644
- this.#gauges.clear();
645
- this.#histograms.clear();
646
- this.#routes.clear();
647
- }
648
- toPrometheus(prefix = "lunibee_rest") {
649
- const lines = [];
650
- const typed = new Set;
651
- const type = (name, kind) => {
652
- if (typed.has(name))
653
- return;
654
- typed.add(name);
655
- lines.push(`# TYPE ${name} ${kind}`);
656
- };
657
- for (const [key, value] of this.#counters) {
658
- const name = `${prefix}_${key.split("{")[0]}`;
659
- type(name, "counter");
660
- lines.push(`${prefix}_${key} ${value}`);
661
- }
662
- for (const [key, value] of this.#gauges) {
663
- type(`${prefix}_${key}`, "gauge");
664
- lines.push(`${prefix}_${key} ${value}`);
665
- }
666
- for (const [key, h] of this.#histograms) {
667
- const [base, rawLabels = ""] = key.split("{");
668
- const labels = rawLabels.replace(/\}$/, "");
669
- const name = `${prefix}_${base}`;
670
- type(name, "histogram");
671
- const join = (extra) => `{${[labels, extra].filter(Boolean).join(",")}}`;
672
- let running = 0;
673
- LATENCY_BOUNDS.forEach((bound, i) => {
674
- running += h.counts[i];
675
- lines.push(`${name}_bucket${join(`le="${bound}"`)} ${running}`);
676
- });
677
- lines.push(`${name}_bucket${join('le="+Inf"')} ${h.count}`);
678
- lines.push(`${name}_sum${labels ? `{${labels}}` : ""} ${h.sum}`);
679
- lines.push(`${name}_count${labels ? `{${labels}}` : ""} ${h.count}`);
680
- }
681
- return `${lines.join(`
682
- `)}
683
- `;
684
- }
685
- }
686
- function metricKey(name, labels) {
687
- const entries = Object.entries(labels);
688
- if (entries.length === 0)
689
- return name;
690
- return `${name}{${entries.map(([k, v]) => `${k}="${v.replace(/["\\\n]/g, "_")}"`).join(",")}}`;
691
- }
692
-
693
- // packages/rest/src/limiter.ts
694
- var DEFAULT_MAX_BACKOFF_MS = 600000;
695
- var MAX_IDLE_REFUSALS = 16;
696
- var MAX_CACHED_ROUTES = 1e4;
697
-
698
- class RateLimiter {
699
- #store;
700
- #maxBackoff;
701
- #telemetry;
702
- #probeMs;
703
- #routes = new Map;
704
- #globalLimit;
705
- #localGlobal = new Map;
706
- constructor(store, options = {}) {
707
- this.#store = store;
708
- this.#maxBackoff = options.maxBackoff ?? DEFAULT_MAX_BACKOFF_MS;
709
- if (!(this.#maxBackoff >= 0))
710
- throw new RangeError("maxBackoff must be a non-negative number.");
711
- this.#probeMs = options.probeMs ?? DEFAULT_PROBE_MS;
712
- if (!(this.#probeMs > 0))
713
- throw new RangeError("probeMs must be a positive number.");
714
- this.#telemetry = options.telemetry;
715
- this.#globalLimit = options.globalLimit ?? 0;
716
- if (!(this.#globalLimit >= 0) || !Number.isFinite(this.#globalLimit))
717
- throw new RangeError("globalLimit must be a non-negative number.");
718
- }
719
- get maxBackoff() {
720
- return this.#maxBackoff;
721
- }
722
- #capWait(ms) {
723
- return Math.min(Math.max(0, ms), this.#maxBackoff);
724
- }
725
- get store() {
726
- return this.#store;
727
- }
728
- get reserves() {
729
- return typeof this.#store.reserve === "function";
730
- }
731
- #cacheRoute(route, hash) {
732
- this.#routes.delete(route);
733
- this.#routes.set(route, hash);
734
- if (this.#routes.size > MAX_CACHED_ROUTES)
735
- this.#routes.delete(this.#routes.keys().next().value);
736
- }
737
- async resolveBucketKey(route, major, identity) {
738
- let hash = this.#routes.get(`${route}\x00${major}`) ?? this.#routes.get(route);
739
- if (hash === undefined) {
740
- hash = await this.#store.getBucketHash(route);
741
- if (hash !== undefined)
742
- this.#cacheRoute(route, hash);
743
- }
744
- const key = scopeBucket(hash ?? route, major);
745
- return identity === undefined ? key : `${identity}/${key}`;
746
- }
747
- async#call(path, operation, call) {
748
- try {
749
- return await call();
750
- } catch (error) {
751
- if (error instanceof RESTError)
752
- throw error;
753
- throw new RESTError(`The rate-limit store failed (${operation}); request not sent.`, 0, undefined, undefined, { path, kind: "coordination", cause: sanitizeCause(error) });
754
- }
755
- }
756
- async acquire(bucketKey, signal, path, options = {}) {
757
- const store = this.#store;
758
- const global = "global" in options ? options.global : "";
759
- if (!store.reserve) {
760
- await this.#waitGlobal(global ?? "", signal, path);
761
- if (global !== undefined)
762
- await this.#localGlobalSlot(global, signal, path);
763
- const bucket = await this.#call(path, "getBucket", () => store.getBucket(bucketKey));
764
- if (bucket) {
765
- const delay = bucket.resetAt - Date.now();
766
- if (bucket.remaining <= 0 && delay > 0)
767
- await this.#wait("bucket", delay, signal, path);
768
- }
769
- return {
770
- reserved: false,
771
- known: !!bucket && bucket.resetAt > Date.now()
772
- };
773
- }
774
- const started = performance.now();
775
- let idle = 0;
776
- for (;; ) {
777
- if (global !== undefined && !store.reservesGlobal) {
778
- await this.#waitGlobal(global, signal, path);
779
- await this.#localGlobalSlot(global, signal, path);
780
- }
781
- const asked = performance.now();
782
- const reservation = await this.#call(path, "reserve", () => store.reserve(bucketKey, {
783
- global,
784
- probeMs: this.#probeMs,
785
- globalLimit: this.#globalLimit || undefined
786
- }));
787
- if (!reservation || typeof reservation !== "object" || typeof reservation.granted !== "boolean")
788
- throw new RESTError("The rate-limit store returned an invalid reservation; request not sent.", 0, undefined, undefined, { path, kind: "coordination" });
789
- if (reservation.granted) {
790
- let known = reservation.known;
791
- if (known === undefined) {
792
- const bucket = await this.#call(path, "getBucket", () => store.getBucket(bucketKey));
793
- known = !!bucket && bucket.resetAt > Date.now();
794
- }
795
- emitTelemetry(this.#telemetry, {
796
- type: "reservation",
797
- granted: true,
798
- known,
799
- latencyMs: performance.now() - asked
800
- });
801
- return { reserved: true, known, resetAt: reservation.resetAt };
802
- }
803
- emitTelemetry(this.#telemetry, {
804
- type: "reservation",
805
- granted: false,
806
- known: true,
807
- latencyMs: performance.now() - asked
808
- });
809
- let wait = Number(reservation.retryAfterMs);
810
- if (!(wait > 0)) {
811
- if (++idle > MAX_IDLE_REFUSALS)
812
- throw new RESTError("The rate-limit store keeps refusing without a wait; request not sent.", 0, undefined, undefined, { path, kind: "coordination" });
813
- wait = Math.min(100, 2 ** idle);
814
- } else
815
- idle = 0;
816
- if (performance.now() - started + wait > 2 * this.#maxBackoff)
817
- throw new RESTError(`Waited for the rate limit longer than twice maxBackoff (${Math.ceil(this.#maxBackoff / 1000)}s); request not sent.`, 429, undefined, undefined, { path });
818
- await this.#wait(reservation.reason ?? "bucket", wait, signal, path);
819
- }
820
- }
821
- async#wait(reason, delay, signal, path) {
822
- const ms = Math.min(delay, this.#maxBackoff);
823
- await sleep(ms, signal, path);
824
- emitTelemetry(this.#telemetry, {
825
- type: "wait",
826
- reason,
827
- durationMs: ms
828
- });
829
- }
830
- async#localGlobalSlot(scope, signal, path) {
831
- if (!this.#globalLimit)
832
- return;
833
- let log = this.#localGlobal.get(scope);
834
- if (!log) {
835
- this.#localGlobal.set(scope, log = []);
836
- if (this.#localGlobal.size > MAX_CACHED_ROUTES)
837
- this.#localGlobal.delete(this.#localGlobal.keys().next().value);
838
- }
839
- for (;; ) {
840
- const wait = takeGlobalSlot(log, Date.now(), this.#globalLimit);
841
- if (wait === 0)
842
- return;
843
- await this.#wait("global", wait, signal, path);
844
- }
845
- }
846
- async#waitGlobal(scope, signal, path) {
847
- const globalDelay = await this.#call(path, "getGlobalReset", () => this.#store.getGlobalReset(scope)) - Date.now();
848
- if (globalDelay > 0)
849
- await this.#wait("global", globalDelay, signal, path);
850
- }
851
- async noteGlobalReset(resetAt, scope = "") {
852
- const waitMs = this.#capWait(resetAt - Date.now());
853
- if (this.#store.extendGlobalReset) {
854
- await this.#store.extendGlobalReset(scope, waitMs);
855
- return;
856
- }
857
- const current = await this.#store.getGlobalReset(scope);
858
- const target = Date.now() + waitMs;
859
- if (target > current)
860
- await this.#store.setGlobalReset(target, scope);
861
- }
862
- retryAfterMs(response, body = {}) {
863
- const fromBody = body.retry_after;
864
- if (typeof fromBody === "number" && Number.isFinite(fromBody) && fromBody >= 0)
865
- return fromBody * 1000;
866
- const headers = parseRateLimitHeaders(response.headers);
867
- return headers.retryAfterMs ?? headers.resetAfterMs ?? 1000;
868
- }
869
- async applyResponse(response, bucketKey, route, major, options = {}) {
870
- const headers = parseRateLimitHeaders(response.headers);
871
- let currentKey = bucketKey;
872
- if (headers.bucket) {
873
- const previous = this.#routes.get(route);
874
- this.#cacheRoute(`${route}\x00${major}`, headers.bucket);
875
- if (previous !== headers.bucket) {
876
- await this.#store.setBucketHash(route, headers.bucket);
877
- this.#cacheRoute(route, headers.bucket);
878
- emitTelemetry(this.#telemetry, {
879
- type: "bucket",
880
- change: previous === undefined ? "discovered" : "remapped",
881
- route
882
- });
883
- }
884
- const key = scopeBucket(headers.bucket, major);
885
- currentKey = options.identity === undefined ? key : `${options.identity}/${key}`;
886
- }
887
- const limited = response.status === 429;
888
- const retryAfterMs = limited ? this.#capWait(this.retryAfterMs(response, options.body)) : undefined;
889
- const global = limited && (options.body?.global === true || headers.global || headers.scope === "global");
890
- const onGlobal = global && options.global !== undefined;
891
- let resetAfterMs = headers.resetAfterMs;
892
- if (resetAfterMs === undefined && headers.resetAtMs !== undefined)
893
- resetAfterMs = Math.max(0, headers.resetAtMs - Date.now());
894
- const update = {
895
- remaining: headers.remaining,
896
- resetAfterMs: resetAfterMs === undefined ? undefined : this.#capWait(resetAfterMs),
897
- limit: headers.limit,
898
- window: headers.resetAtMs !== undefined && Math.abs(headers.resetAtMs - Date.now()) <= this.#maxBackoff ? headers.resetAtMs : undefined,
899
- retryAfterMs: limited && !onGlobal ? retryAfterMs : undefined
900
- };
901
- if (this.#store.reconcile)
902
- await this.#store.reconcile(currentKey, update);
903
- else {
904
- const next = reconcileState(await this.#store.getBucket(currentKey), update, Date.now());
905
- if (next === null)
906
- await this.#store.updateBucket(currentKey, {
907
- remaining: 0,
908
- resetAt: 0
909
- });
910
- else if (next)
911
- await this.#store.updateBucket(currentKey, next);
912
- }
913
- if (currentKey !== bucketKey && options.grant?.reserved && this.#store.release)
914
- await this.#store.release(bucketKey, options.grant, false);
915
- if (onGlobal)
916
- await this.noteGlobalReset(Date.now() + retryAfterMs, options.global);
917
- return currentKey;
918
- }
919
- async releaseGrant(bucketKey, grant, notSent) {
920
- if (!grant.reserved || !this.#store.release)
921
- return;
922
- try {
923
- await this.#store.release(bucketKey, grant, notSent);
924
- } catch {}
925
- }
926
- }
927
-
928
- // packages/rest/src/identity.ts
929
- import { createHash as createHash2 } from "crypto";
930
- function tokenFingerprint(token) {
931
- return createHash2("sha256").update(token).digest("hex").slice(0, 16);
932
- }
933
- function requestIdentity(path, fingerprint) {
934
- if (/^\/interactions\//i.test(path))
935
- return { bucket: "shared" };
936
- if (/^\/webhooks\/[^/?]+\/[^/?]+/i.test(path))
937
- return { bucket: "shared", global: fingerprint ?? "anon" };
938
- if (!fingerprint)
939
- return { bucket: "anon", global: "anon" };
940
- return { bucket: fingerprint, global: fingerprint };
941
- }
942
-
943
- // packages/rest/src/transport.ts
944
- var LOOPBACK = /^(?:localhost|127(?:\.\d{1,3}){3}|\[::1\])$/i;
945
- function parseBaseURL(raw, allowInsecure = false) {
946
- let url;
947
- try {
948
- url = new URL(raw);
949
- } catch {
950
- throw new TypeError("baseURL must be an absolute http(s) URL.");
951
- }
952
- if (url.protocol !== "https:" && url.protocol !== "http:")
953
- throw new TypeError("baseURL must use https.");
954
- if (url.username || url.password)
955
- throw new TypeError("baseURL must not contain credentials.");
956
- if (url.search || url.hash || /[?#]/.test(raw))
957
- throw new TypeError("baseURL must not contain a query or fragment.");
958
- if (url.protocol === "http:" && !allowInsecure && !LOOPBACK.test(url.hostname))
959
- throw new TypeError("baseURL must use https: the bot token would travel in cleartext (allowInsecureBaseURL: true overrides this for a trusted network).");
960
- return url.href.replace(/\/$/, "");
961
- }
962
-
963
- class TransportError extends Error {
964
- timedOut;
965
- constructor(message, timedOut, cause) {
966
- super(message, cause === undefined ? undefined : { cause });
967
- this.name = "TransportError";
968
- this.timedOut = timedOut;
969
- }
970
- }
971
-
972
- class HttpTransport {
973
- #baseURL;
974
- #timeout;
975
- #fetch;
976
- constructor(options = {}) {
977
- this.#baseURL = parseBaseURL(options.baseURL ?? "https://discord.com/api/v10", options.allowInsecureBaseURL);
978
- this.#timeout = Math.max(1, options.timeout ?? 15000);
979
- this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
980
- }
981
- get baseURL() {
982
- return this.#baseURL;
983
- }
984
- get timeout() {
985
- return this.#timeout;
986
- }
987
- async send(request) {
988
- if (request.signal?.aborted)
989
- throw abortError(request.path, request.signal.reason);
990
- const controller = new AbortController;
991
- const onAbort = () => controller.abort(request.signal?.reason);
992
- request.signal?.addEventListener("abort", onAbort, { once: true });
993
- const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
994
- try {
995
- return await this.#fetch(`${this.#baseURL}${request.path}`, {
996
- method: request.method,
997
- headers: request.headers,
998
- body: request.body,
999
- signal: controller.signal,
1000
- redirect: "error"
1001
- });
1002
- } catch (error) {
1003
- if (error instanceof RESTError)
1004
- throw error;
1005
- const timedOut = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
1006
- throw new TransportError(timedOut ? `Discord REST request timed out after ${this.#timeout}ms` : "Discord REST request failed", timedOut, sanitizeCause(error));
1007
- } finally {
1008
- clearTimeout(timer);
1009
- request.signal?.removeEventListener("abort", onAbort);
1010
- }
1011
- }
1012
- }
1013
-
1014
- // packages/rest/src/decoder.ts
1015
- class ResponseDecoder {
1016
- constructor() {}
1017
- async read(response) {
1018
- if (response.status === 204)
1019
- return;
1020
- const contentType = response.headers.get("content-type") ?? "";
1021
- if (contentType.includes("application/json"))
1022
- return response.json().catch(() => {
1023
- return;
1024
- });
1025
- return response.text().catch(() => {
1026
- return;
1027
- });
1028
- }
1029
- errorData(payload) {
1030
- if (!payload || typeof payload !== "object")
1031
- return {};
1032
- const data = payload;
1033
- return {
1034
- message: typeof data.message === "string" ? data.message : undefined,
1035
- code: typeof data.code === "number" ? data.code : undefined,
1036
- retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
1037
- global: data.global === true
1038
- };
1039
- }
1040
- retryAfter(response, data) {
1041
- const raw = response.headers.get("Retry-After");
1042
- const header = raw === null ? Number.NaN : Number(raw);
1043
- const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
1044
- return Math.max(0, retryAfter);
1045
- }
1046
- }
1047
-
1048
- // packages/rest/src/routes.ts
1049
- var Routes = {
1050
- user: () => "/users/@me",
1051
- userById: (userId) => `/users/${snowflake(userId, "User ID")}`,
1052
- guild: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}`,
1053
- currentUserGuild: (guildId) => `/users/@me/guilds/${snowflake(guildId, "Guild ID")}`,
1054
- sticker: (stickerId) => `/stickers/${snowflake(stickerId, "Sticker ID")}`,
1055
- messageReactionUser: (channelId, messageId, emoji, userId) => `${Routes.messageReactions(channelId, messageId, emoji)}/${snowflake(userId, "User ID")}`,
1056
- guildPreview: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/preview`,
1057
- guildActiveThreads: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/threads/active`,
1058
- guildWebhooks: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/webhooks`,
1059
- guildInvites: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/invites`,
1060
- guildChannels: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/channels`,
1061
- guildMember: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/${snowflake(userId, "User ID")}`,
1062
- guildRoles: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/roles`,
1063
- guildRole: (guildId, roleId) => `/guilds/${snowflake(guildId, "Guild ID")}/roles/${snowflake(roleId, "Role ID")}`,
1064
- guildMemberRole: (guildId, userId, roleId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/${snowflake(userId, "User ID")}/roles/${snowflake(roleId, "Role ID")}`,
1065
- guildBans: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/bans`,
1066
- guildBan: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/bans/${snowflake(userId, "User ID")}`,
1067
- channel: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}`,
1068
- channelMessages: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages`,
1069
- message: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/${snowflake(messageId, "Message ID")}`,
1070
- crosspostMessage: (channelId, messageId) => `${Routes.message(channelId, messageId)}/crosspost`,
1071
- messageReactions: (channelId, messageId, emoji) => `${Routes.message(channelId, messageId)}/reactions/${encodeURIComponent(emoji)}`,
1072
- messageReactionMe: (channelId, messageId, emoji) => `${Routes.messageReactions(channelId, messageId, emoji)}/@me`,
1073
- messageReactionsAll: (channelId, messageId) => `${Routes.message(channelId, messageId)}/reactions`,
1074
- channelMessagesPins: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/pins`,
1075
- channelMessagesPin: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/pins/${snowflake(messageId, "Message ID")}`,
1076
- messageThread: (channelId, messageId) => `${Routes.message(channelId, messageId)}/threads`,
1077
- channelBulkDelete: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/bulk-delete`,
1078
- channelWebhooks: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/webhooks`,
1079
- channelInvites: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/invites`,
1080
- webhook: (webhookId, token) => `/webhooks/${snowflake(webhookId, "Webhook ID")}${token ? `/${encodeURIComponent(token)}` : ""}`,
1081
- webhookMessage: (webhookId, token, messageId) => `/webhooks/${snowflake(webhookId, "Webhook ID")}/${encodeURIComponent(token)}/messages/${snowflake(messageId, "Message ID")}`,
1082
- applicationCommands: (applicationId) => `/applications/${snowflake(applicationId, "Application ID")}/commands`,
1083
- applicationCommand: (applicationId, commandId) => `/applications/${snowflake(applicationId, "Application ID")}/commands/${snowflake(commandId, "Command ID")}`,
1084
- guildApplicationCommands: (applicationId, guildId) => `/applications/${snowflake(applicationId, "Application ID")}/guilds/${snowflake(guildId, "Guild ID")}/commands`,
1085
- guildApplicationCommand: (applicationId, guildId, commandId) => `/applications/${snowflake(applicationId, "Application ID")}/guilds/${snowflake(guildId, "Guild ID")}/commands/${snowflake(commandId, "Command ID")}`,
1086
- interactionCallback: (interactionId, token) => `/interactions/${snowflake(interactionId, "Interaction ID")}/${encodeURIComponent(token)}/callback`,
1087
- interactionOriginalResponse: (applicationId, token) => `/webhooks/${snowflake(applicationId, "Application ID")}/${encodeURIComponent(token)}/messages/@original`,
1088
- channelPublicArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads/archived/public`,
1089
- channelPrivateArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads/archived/private`,
1090
- channelJoinedPrivateArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/users/@me/threads/archived/private`,
1091
- threadMember: (threadId, userId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members/${userOrMe(userId)}`,
1092
- channelTyping: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/typing`,
1093
- channelThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads`,
1094
- threadMembers: (threadId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members`,
1095
- guildScheduledEvents: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events`,
1096
- guildScheduledEvent: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}`,
1097
- guildScheduledEventUsers: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}/users`,
1098
- channelPermission: (channelId, overwriteId) => `/channels/${snowflake(channelId, "Channel ID")}/permissions/${snowflake(overwriteId, "Overwrite ID")}`,
1099
- guildAutoModerationRules: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules`,
1100
- guildAutoModerationRule: (guildId, ruleId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules/${snowflake(ruleId, "Rule ID")}`,
1101
- voiceRegions: () => "/voice/regions",
1102
- gateway: () => "/gateway",
1103
- gatewayBot: () => "/gateway/bot",
1104
- guildAuditLog: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/audit-logs`,
1105
- guildMembers: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/members`,
1106
- guildMembersSearch: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/search`,
1107
- guildMemberMe: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/@me`,
1108
- guildBulkBan: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/bulk-ban`,
1109
- guildVoiceState: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/voice-states/${userOrMe(userId)}`,
1110
- guildIntegration: (guildId, integrationId) => `/guilds/${snowflake(guildId, "Guild ID")}/integrations/${snowflake(integrationId, "Integration ID")}`,
1111
- userGuilds: () => "/users/@me/guilds",
1112
- stickerPacks: (packId) => packId ? `/sticker-packs/${snowflake(packId, "Sticker pack ID")}` : "/sticker-packs",
1113
- guildWelcomeScreen: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/welcome-screen`,
1114
- guildOnboarding: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/onboarding`,
1115
- guildPrune: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/prune`,
1116
- stageInstances: () => "/stage-instances",
1117
- stageInstanceByChannel: (channelId) => `/stage-instances/${snowflake(channelId, "Channel ID")}`,
1118
- invite: (code) => `/invites/${encodeURIComponent(code)}`,
1119
- guildVanity: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/vanity-url`,
1120
- guildEmoji: (guildId, emojiId) => emojiId ? `/guilds/${snowflake(guildId, "Guild ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/emojis`,
1121
- applicationEmoji: (applicationId, emojiId) => emojiId ? `/applications/${snowflake(applicationId, "Application ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/emojis`,
1122
- pollExpire: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/polls/${snowflake(messageId, "Message ID")}/expire`,
1123
- pollAnswerVoters: (channelId, messageId, answerId) => `/channels/${snowflake(channelId, "Channel ID")}/polls/${snowflake(messageId, "Message ID")}/answers/${integer(answerId, "Answer ID")}`,
1124
- guildSticker: (guildId, stickerId) => stickerId ? `/guilds/${snowflake(guildId, "Guild ID")}/stickers/${snowflake(stickerId, "Sticker ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/stickers`,
1125
- sendSoundboardSound: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/send-soundboard-sound`,
1126
- soundboardDefaultSounds: () => "/soundboard-default-sounds",
1127
- guildSoundboardSound: (guildId, soundId) => soundId ? `/guilds/${snowflake(guildId, "Guild ID")}/soundboard-sounds/${snowflake(soundId, "Sound ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/soundboard-sounds`,
1128
- applicationSkus: (applicationId) => `/applications/${snowflake(applicationId, "Application ID")}/skus`,
1129
- applicationEntitlement: (applicationId, entitlementId) => entitlementId ? `/applications/${snowflake(applicationId, "Application ID")}/entitlements/${snowflake(entitlementId, "Entitlement ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/entitlements`,
1130
- consumeEntitlement: (applicationId, entitlementId) => `/applications/${snowflake(applicationId, "Application ID")}/entitlements/${snowflake(entitlementId, "Entitlement ID")}/consume`,
1131
- skuSubscription: (skuId, subscriptionId) => subscriptionId ? `/skus/${snowflake(skuId, "SKU ID")}/subscriptions/${snowflake(subscriptionId, "Subscription ID")}` : `/skus/${snowflake(skuId, "SKU ID")}/subscriptions`
1132
- };
1133
- function userOrMe(value) {
1134
- return value === "@me" ? value : snowflake(value, "User ID");
1135
- }
1136
- function integer(value, field) {
1137
- if (!Number.isInteger(value) || value < 0)
1138
- throw new TypeError(`${field} must be a non-negative integer.`);
1139
- return String(value);
1140
- }
1141
- function snowflake(value, field) {
1142
- if (!/^\d{1,20}$/.test(value))
1143
- throw new TypeError(`${field} must be a valid Discord snowflake.`);
1144
- return value;
1145
- }
1146
- // packages/rest/src/error-codes.ts
1147
- var RESTErrorCode = {
1148
- UnknownAccount: 10001,
1149
- UnknownChannel: 10003,
1150
- UnknownGuild: 10004,
1151
- UnknownInvite: 10006,
1152
- UnknownMember: 10007,
1153
- UnknownMessage: 10008,
1154
- UnknownPermissionOverwrite: 10009,
1155
- UnknownRole: 10011,
1156
- UnknownUser: 10013,
1157
- UnknownEmoji: 10014,
1158
- UnknownWebhook: 10015,
1159
- UnknownBan: 10026,
1160
- UnknownSticker: 10060,
1161
- UnknownInteraction: 10062,
1162
- UnknownScheduledEvent: 10070,
1163
- BotsCannotUseEndpoint: 20001,
1164
- MaxGuilds: 30001,
1165
- MaxPins: 30003,
1166
- MaxRoles: 30005,
1167
- MaxWebhooks: 30007,
1168
- MaxEmojis: 30008,
1169
- MaxReactions: 30010,
1170
- MaxChannels: 30013,
1171
- InteractionAlreadyAcknowledged: 40060,
1172
- MissingAccess: 50001,
1173
- CannotExecuteInDM: 50003,
1174
- CannotEditOthersMessage: 50005,
1175
- CannotSendEmptyMessage: 50006,
1176
- CannotSendMessagesToUser: 50007,
1177
- MissingPermissions: 50013,
1178
- InvalidToken: 50014,
1179
- MessageTooOldToBulkDelete: 50034,
1180
- InvalidFormBody: 50035,
1181
- ThreadArchived: 50083,
1182
- CannotReplyWithoutReadHistory: 160002
1183
- };
1184
- // packages/rest/src/redis.ts
1185
- var LUA_PRELUDE = `
1186
- local function now_ms()
1187
- local t = redis.call('TIME')
1188
- return tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)
1189
- end
1190
- local function load(key)
1191
- local raw = redis.call('GET', key)
1192
- if not raw then return nil end
1193
- local ok, s = pcall(cjson.decode, raw)
1194
- if ok and type(s) == 'table' and type(s.resetAt) == 'number'
1195
- and type(s.remaining) == 'number' then return s end
1196
- return nil
1197
- end
1198
- local function save(key, s, now, linger)
1199
- local ttl = math.max(1, math.ceil(s.resetAt - now)) + linger
1200
- redis.call('SET', key, cjson.encode(s), 'PX', ttl)
1201
- end
1202
- `;
1203
- var RESERVE_SCRIPT = `${LUA_PRELUDE}
1204
- local now = now_ms()
1205
- local glimit, gwindow = tonumber(ARGV[4]) or 0, tonumber(ARGV[5]) or 1000
1206
- if #KEYS > 1 then
1207
- local g = tonumber(redis.call('GET', KEYS[2]) or '0') or 0
1208
- if g > now then return {0, math.ceil(g - now), 'global'} end
1209
- end
1210
- if #KEYS < 3 then glimit = 0 end
1211
- -- A sliding log of send times: at most glimit in any gwindow span.
1212
- if glimit > 0 then
1213
- redis.call('ZREMRANGEBYSCORE', KEYS[3], '-inf', now - gwindow)
1214
- if redis.call('ZCARD', KEYS[3]) >= glimit then
1215
- local oldest = redis.call('ZRANGE', KEYS[3], 0, 0, 'WITHSCORES')
1216
- return {0, math.max(1, tonumber(oldest[2]) + gwindow - now + 1), 'global'}
1217
- end
1218
- end
1219
- local function count()
1220
- if glimit > 0 then
1221
- local seq = redis.call('ZCARD', KEYS[3])
1222
- redis.call('ZADD', KEYS[3], now, now .. ':' .. seq)
1223
- redis.call('PEXPIRE', KEYS[3], gwindow + 1000)
1224
- end
1225
- end
1226
- local probe, poll, linger = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
1227
- local s = load(KEYS[1])
1228
- if s and s.resetAt > now then
1229
- if s.remaining > 0 then
1230
- s.remaining = s.remaining - 1
1231
- save(KEYS[1], s, now, linger)
1232
- count()
1233
- return {1, s.resetAt, 1}
1234
- end
1235
- local wait = s.resetAt - now
1236
- if s.provisional then wait = math.min(wait, poll) end
1237
- local reason = 'bucket'
1238
- if s.probe then reason = 'probe' end
1239
- return {0, math.ceil(wait), reason}
1240
- end
1241
- local resetAt = now + probe
1242
- if s and type(s.limit) == 'number' and s.limit > 0 then
1243
- save(KEYS[1], {remaining = s.limit - 1, resetAt = resetAt, limit = s.limit, provisional = true}, now, linger)
1244
- count()
1245
- return {1, resetAt, 1}
1246
- end
1247
- save(KEYS[1], {remaining = 0, resetAt = resetAt, provisional = true, probe = true}, now, 0)
1248
- count()
1249
- return {1, resetAt, 0}
1250
- `;
1251
- var RECONCILE_SCRIPT = `${LUA_PRELUDE}
1252
- local now = now_ms()
1253
- local rem, after, limit = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
1254
- local win, retry, linger = tonumber(ARGV[4]), tonumber(ARGV[5]), tonumber(ARGV[6])
1255
- local tol = 50
1256
- local incoming = nil
1257
- if after then incoming = math.ceil(now + after) end
1258
- local s = load(KEYS[1])
1259
- local n
1260
- if s and s.resetAt > now and not s.probe then
1261
- n = s
1262
- local stale = win and n.window and win < n.window - tol
1263
- if not stale then
1264
- if rem then n.remaining = math.min(n.remaining, rem) end
1265
- if incoming then
1266
- local newer = win and n.window and win > n.window + tol
1267
- if n.provisional or newer then n.resetAt = incoming
1268
- else n.resetAt = math.max(n.resetAt, incoming) end
1269
- n.provisional = nil
1270
- end
1271
- if win then n.window = math.max(n.window or 0, win) end
1272
- end
1273
- else
1274
- if not rem and not retry then
1275
- if s and s.probe then redis.call('DEL', KEYS[1]) end
1276
- return 0
1277
- end
1278
- n = {remaining = rem or 0, resetAt = incoming or now}
1279
- if s and type(s.limit) == 'number' then n.limit = s.limit end
1280
- if win then n.window = win end
1281
- end
1282
- if limit and limit > 0 then n.limit = limit end
1283
- if retry then
1284
- n.remaining = 0
1285
- n.resetAt = math.max(n.resetAt, math.ceil(now + retry))
1286
- n.provisional = nil
1287
- end
1288
- save(KEYS[1], n, now, linger)
1289
- return 1
1290
- `;
1291
- var EXTEND_GLOBAL_SCRIPT = `${LUA_PRELUDE}
1292
- local now = now_ms()
1293
- local target = math.ceil(now + tonumber(ARGV[1]))
1294
- local cur = tonumber(redis.call('GET', KEYS[1]) or '0') or 0
1295
- if target > cur then
1296
- redis.call('SET', KEYS[1], tostring(target), 'PX', math.max(1, target - now))
1297
- return target - now
1298
- end
1299
- return math.max(0, cur - now)
1300
- `;
1301
- var RELEASE_SCRIPT = `${LUA_PRELUDE}
1302
- local s = load(KEYS[1])
1303
- if not s or s.resetAt ~= tonumber(ARGV[1]) then return 0 end
1304
- if s.probe then
1305
- redis.call('DEL', KEYS[1])
1306
- return 1
1307
- end
1308
- if ARGV[2] == '1' then
1309
- local cap = s.remaining + 1
1310
- if type(s.limit) == 'number' then cap = math.min(cap, s.limit) end
1311
- s.remaining = cap
1312
- redis.call('SET', KEYS[1], cjson.encode(s), 'KEEPTTL')
1313
- return 1
1314
- end
1315
- return 0
1316
- `;
1317
- var VERIFY_SCRIPT = "return redis.call('TIME')[1]";
1318
- function fromBunRedis(client) {
1319
- return {
1320
- get: (key) => client.send("GET", [key]),
1321
- mget: (keys) => client.send("MGET", keys),
1322
- set: (key, value, mode, duration) => client.send("SET", mode === undefined ? [key, String(value)] : [key, String(value), mode, String(duration)]),
1323
- del: (...keys) => client.send("DEL", keys),
1324
- eval: (script, numKeys, ...args) => client.send("EVAL", [script, String(numKeys), ...args.map(String)])
1325
- };
1326
- }
1327
- var SCHEMA = "v1";
1328
-
1329
- class RedisRateLimitStore {
1330
- #client;
1331
- #prefix;
1332
- #onErrorCallback;
1333
- #policy;
1334
- #timeout;
1335
- #telemetry;
1336
- #atomic;
1337
- #local = new MemoryRateLimitStore;
1338
- #lastError;
1339
- #warnedSlots = false;
1340
- #lastFailureAt;
1341
- #failures = 0;
1342
- #mode;
1343
- constructor(options) {
1344
- this.#client = options.client;
1345
- this.#prefix = `${options.prefix ?? "lunibee:rest:"}${SCHEMA}:`;
1346
- this.#onErrorCallback = options.onError;
1347
- this.#policy = options.failurePolicy ?? "degraded-local";
1348
- if (this.#policy !== "degraded-local" && this.#policy !== "fail-closed")
1349
- throw new TypeError('failurePolicy must be "degraded-local" or "fail-closed".');
1350
- this.#timeout = options.commandTimeout ?? 2000;
1351
- if (!(this.#timeout > 0))
1352
- throw new RangeError("commandTimeout must be a positive number.");
1353
- this.#telemetry = options.telemetry;
1354
- this.#atomic = typeof options.client.eval === "function";
1355
- if (options.requireAtomic && !this.#atomic)
1356
- throw new TypeError("requireAtomic: the Redis client has no eval method, so reservations cannot be atomic.");
1357
- this.#mode = this.#atomic ? "distributed" : "observational";
1358
- if (this.#atomic) {
1359
- this.reserve = (key, opts) => this.#reserve(key, opts);
1360
- this.reconcile = (key, update) => this.#reconcile(key, update);
1361
- this.extendGlobalReset = (scope, waitMs) => this.#extendGlobal(scope, waitMs);
1362
- this.release = (key, grant, refund) => this.#release(key, grant, refund);
1363
- this.reservesGlobal = true;
1364
- }
1365
- }
1366
- get lastError() {
1367
- return this.#lastError;
1368
- }
1369
- isHealthy() {
1370
- return this.#lastError === undefined;
1371
- }
1372
- get mode() {
1373
- return this.#mode;
1374
- }
1375
- get supportsReservation() {
1376
- return this.#atomic;
1377
- }
1378
- diagnostics() {
1379
- const error = this.#lastError;
1380
- return {
1381
- mode: this.#mode,
1382
- failurePolicy: this.#policy,
1383
- atomic: this.#atomic,
1384
- failures: this.#failures,
1385
- lastFailureAt: this.#lastFailureAt,
1386
- lastError: error === undefined ? undefined : String(sanitizeCause(error)?.message ?? error)
1387
- };
1388
- }
1389
- async verify() {
1390
- if (!this.#atomic)
1391
- throw new TypeError("The Redis client has no eval method: reservations cannot be atomic.");
1392
- try {
1393
- await this.#withTimeout(this.#client.eval(VERIFY_SCRIPT, 0));
1394
- } catch (error) {
1395
- throw new RESTError("The Redis rate-limit store cannot run its scripts.", 0, undefined, undefined, { kind: "coordination", cause: sanitizeCause(error) });
1396
- }
1397
- }
1398
- #setMode(mode) {
1399
- if (mode === this.#mode)
1400
- return;
1401
- const previous = this.#mode;
1402
- this.#mode = mode;
1403
- emitTelemetry(this.#telemetry, { type: "mode", mode, previous });
1404
- console.warn(mode === "degraded-local" ? "[lunibee/rest] Redis rate-limit store unreachable: limiting locally in this process until it answers." : mode === "unavailable" ? "[lunibee/rest] Redis rate-limit store unreachable: requests fail until it answers (fail-closed)." : "[lunibee/rest] Redis rate-limit store reachable again: limits are shared.");
1405
- }
1406
- #withTimeout(promise) {
1407
- let timer;
1408
- return Promise.race([
1409
- promise,
1410
- new Promise((_, reject) => {
1411
- timer = setTimeout(() => reject(new Error(`Redis command timed out after ${this.#timeout}ms`)), this.#timeout);
1412
- })
1413
- ]).finally(() => clearTimeout(timer));
1414
- }
1415
- async#run(operation, command, fallback, enforcing) {
1416
- const started = performance.now();
1417
- try {
1418
- const value = await this.#withTimeout(command());
1419
- emitTelemetry(this.#telemetry, {
1420
- type: "store",
1421
- operation,
1422
- latencyMs: performance.now() - started,
1423
- ok: true
1424
- });
1425
- this.#lastError = undefined;
1426
- this.#setMode(this.#atomic ? "distributed" : "observational");
1427
- return value;
1428
- } catch (error) {
1429
- emitTelemetry(this.#telemetry, {
1430
- type: "store",
1431
- operation,
1432
- latencyMs: performance.now() - started,
1433
- ok: false
1434
- });
1435
- this.#lastError = error;
1436
- this.#lastFailureAt = Date.now();
1437
- this.#failures++;
1438
- if (!this.#warnedSlots && /CROSSSLOT/.test(String(error))) {
1439
- this.#warnedSlots = true;
1440
- console.warn('[lunibee/rest] Redis Cluster refused a script whose keys hash to different slots (CROSSSLOT): use a hash-tagged prefix such as "{mybot}:rest:".');
1441
- }
1442
- try {
1443
- this.#onErrorCallback?.(operation, error);
1444
- } catch {}
1445
- if (this.#policy === "fail-closed") {
1446
- this.#setMode("unavailable");
1447
- if (enforcing)
1448
- throw new RESTError(`The Redis rate-limit store is unavailable (${operation}); request not sent (failurePolicy: "fail-closed").`, 0, undefined, undefined, { kind: "coordination", cause: sanitizeCause(error) });
1449
- } else
1450
- this.#setMode("degraded-local");
1451
- return fallback();
1452
- }
1453
- }
1454
- #bucketKey(key) {
1455
- return `${this.#prefix}bucket:${key}`;
1456
- }
1457
- #globalKey(scope) {
1458
- return `${this.#prefix}global:${scope}`;
1459
- }
1460
- async getBucketHash(route) {
1461
- return this.#run("getBucketHash", async () => await this.#client.get(`${this.#prefix}route:${route}`) ?? undefined, () => this.#local.getBucketHash(route), false);
1462
- }
1463
- async setBucketHash(route, hash) {
1464
- this.#local.setBucketHash(route, hash);
1465
- await this.#run("setBucketHash", async () => {
1466
- await this.#client.set(`${this.#prefix}route:${route}`, hash, "EX", 604800);
1467
- }, () => {
1468
- return;
1469
- }, false);
1470
- }
1471
- async getBucket(key) {
1472
- return this.#run("getBucket", async () => {
1473
- const data = await this.#client.get(this.#bucketKey(key));
1474
- if (!data)
1475
- return;
1476
- try {
1477
- return JSON.parse(data);
1478
- } catch {
1479
- return;
1480
- }
1481
- }, () => this.#local.getBucket(key), true);
1482
- }
1483
- async updateBucket(key, state) {
1484
- this.#local.updateBucket(key, state);
1485
- const ttl = Math.ceil(state.resetAt - Date.now());
1486
- await this.#run("updateBucket", async () => {
1487
- if (ttl <= 0)
1488
- await this.#client.del(this.#bucketKey(key));
1489
- else
1490
- await this.#client.set(this.#bucketKey(key), JSON.stringify(state), "PX", ttl);
1491
- }, () => {
1492
- return;
1493
- }, false);
1494
- }
1495
- reserve;
1496
- reconcile;
1497
- extendGlobalReset;
1498
- release;
1499
- reservesGlobal;
1500
- async#reserve(key, options = {}) {
1501
- const keys = [this.#bucketKey(key)];
1502
- if (options.global !== undefined) {
1503
- keys.push(this.#globalKey(options.global));
1504
- if (options.globalLimit)
1505
- keys.push(`${this.#prefix}global-log:${options.global}`);
1506
- }
1507
- return this.#run("reserve", async () => {
1508
- const result = await this.#client.eval(RESERVE_SCRIPT, keys.length, ...keys, options.probeMs ?? DEFAULT_PROBE_MS, PROBE_POLL_MS, BUCKET_LINGER_MS, options.globalLimit ?? 0, GLOBAL_WINDOW_MS);
1509
- return parseReservation(result);
1510
- }, () => this.#local.reserve(key, options), true);
1511
- }
1512
- async#reconcile(key, update) {
1513
- this.#local.reconcile(key, update);
1514
- const arg = (value) => value === undefined ? "" : String(value);
1515
- await this.#run("reconcile", async () => {
1516
- await this.#client.eval(RECONCILE_SCRIPT, 1, this.#bucketKey(key), arg(update.remaining), arg(update.resetAfterMs), arg(update.limit), arg(update.window), arg(update.retryAfterMs), BUCKET_LINGER_MS);
1517
- }, () => {
1518
- return;
1519
- }, false);
1520
- }
1521
- async#extendGlobal(scope, waitMs) {
1522
- this.#local.extendGlobalReset(scope, waitMs);
1523
- await this.#run("extendGlobalReset", async () => {
1524
- await this.#client.eval(EXTEND_GLOBAL_SCRIPT, 1, this.#globalKey(scope), Math.max(0, Math.ceil(waitMs)));
1525
- }, () => {
1526
- return;
1527
- }, false);
1528
- }
1529
- async#release(key, grant, refund) {
1530
- this.#local.release(key, grant, refund);
1531
- if (grant.resetAt === undefined)
1532
- return;
1533
- await this.#run("release", async () => {
1534
- await this.#client.eval(RELEASE_SCRIPT, 1, this.#bucketKey(key), grant.resetAt, refund ? "1" : "0");
1535
- }, () => {
1536
- return;
1537
- }, false);
1538
- }
1539
- async getGlobalReset(scope = "") {
1540
- return this.#run("getGlobalReset", async () => {
1541
- const resetAt = await this.#client.get(this.#globalKey(scope));
1542
- return resetAt ? Number(resetAt) || 0 : 0;
1543
- }, () => this.#local.getGlobalReset(scope), true);
1544
- }
1545
- async setGlobalReset(resetAt, scope = "") {
1546
- this.#local.setGlobalReset(resetAt, scope);
1547
- const ttl = Math.ceil(resetAt - Date.now());
1548
- await this.#run("setGlobalReset", async () => {
1549
- if (ttl <= 0)
1550
- await this.#client.del(this.#globalKey(scope));
1551
- else
1552
- await this.#client.set(this.#globalKey(scope), String(resetAt), "PX", ttl);
1553
- }, () => {
1554
- return;
1555
- }, false);
1556
- }
1557
- }
1558
- function parseReservation(result) {
1559
- if (!Array.isArray(result) || result.length < 3)
1560
- throw new Error("Unexpected reply from the reservation script.");
1561
- const [granted, value, flag] = result;
1562
- const n = Number(value);
1563
- if (!Number.isFinite(n))
1564
- throw new Error("Unexpected reply from the reservation script.");
1565
- if (Number(granted) === 1)
1566
- return { granted: true, known: Number(flag) === 1, resetAt: n };
1567
- const reason = String(flag);
1568
- return {
1569
- granted: false,
1570
- retryAfterMs: Math.max(0, n),
1571
- reason: reason === "global" || reason === "probe" ? reason : "bucket"
1572
- };
1573
- }
1574
- // packages/rest/src/webhook.ts
1575
- class WebhookClient {
1576
- id;
1577
- token;
1578
- #rest;
1579
- constructor(options, restOptions) {
1580
- if (options.url) {
1581
- const match = options.url.match(/^https:\/\/(?:(?:ptb|canary)\.)?discord(?:app)?\.com\/api(?:\/v\d+)?\/webhooks\/(\d{1,20})\/([A-Za-z0-9_-]+)\/?(?:[?#].*)?$/);
1582
- if (!match)
1583
- throw new Error("Invalid Discord webhook URL format.");
1584
- this.id = match[1];
1585
- this.token = match[2];
1586
- } else if (options.id && options.token) {
1587
- this.id = options.id;
1588
- this.token = options.token;
1589
- } else {
1590
- throw new Error("WebhookClient requires either a webhook 'url' or both 'id' and 'token'.");
1591
- }
1592
- this.#rest = new REST(restOptions);
1593
- }
1594
- #prepare(options, base = {}) {
1595
- const payload = typeof options === "string" ? { content: options } : options;
1596
- const {
1597
- thread_id: threadWire,
1598
- threadId,
1599
- avatarURL,
1600
- allowedMentions,
1601
- files,
1602
- ...body
1603
- } = payload;
1604
- const thread_id = threadWire ?? threadId;
1605
- if (avatarURL !== undefined && body.avatar_url === undefined)
1606
- body.avatar_url = avatarURL;
1607
- if (allowedMentions !== undefined && body.allowed_mentions === undefined)
1608
- body.allowed_mentions = allowedMentions;
1609
- const params = new URLSearchParams(base);
1610
- if (thread_id)
1611
- params.set("thread_id", String(thread_id));
1612
- const json = { ...body };
1613
- if (body.embeds)
1614
- json.embeds = body.embeds.map((e) => ("toJSON" in e) ? e.toJSON() : e);
1615
- if (body.components)
1616
- json.components = body.components.map((c) => ("toJSON" in c) ? c.toJSON() : c);
1617
- const text = params.toString();
1618
- return { query: text ? `?${text}` : "", json, files };
1619
- }
1620
- async send(options) {
1621
- const { query, json, files } = this.#prepare(options, { wait: "true" });
1622
- const path = `${Routes.webhook(this.id, this.token)}${query}`;
1623
- return files?.length ? this.#rest.postWithFiles(path, json, files) : this.#rest.post(path, json);
1624
- }
1625
- async fetchMessage(messageId, options = {}) {
1626
- const query = options.threadId ? `?${new URLSearchParams({ thread_id: options.threadId })}` : "";
1627
- return this.#rest.get(`${Routes.webhookMessage(this.id, this.token, messageId)}${query}`);
1628
- }
1629
- async editMessage(messageId, options) {
1630
- const { query, json, files } = this.#prepare(options);
1631
- const path = `${Routes.webhookMessage(this.id, this.token, messageId)}${query}`;
1632
- return files?.length ? this.#rest.patchWithFiles(path, json, files) : this.#rest.patch(path, json);
1633
- }
1634
- async deleteMessage(messageId, options = {}) {
1635
- const query = options.threadId ? `?${new URLSearchParams({ thread_id: options.threadId })}` : "";
1636
- await this.#rest.delete(`${Routes.webhookMessage(this.id, this.token, messageId)}${query}`);
1637
- }
1638
- }
1639
-
1640
- // packages/rest/src/index.ts
1641
- var LIBRARY_VERSION = package_default.version;
1642
- var LIBRARY_URL = "https://github.com/Ekretos/lunibee";
1643
- var USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${LIBRARY_VERSION})`;
1644
- function assertSafePath(path) {
1645
- if (/[\u0000-\u0020\u007f#]/.test(path))
1646
- throw new TypeError("REST paths must not contain control characters, spaces or '#'.");
1647
- const end = path.indexOf("?");
1648
- const pathname = end === -1 ? path : path.slice(0, end);
1649
- if (pathname.includes("\\"))
1650
- throw new TypeError("REST paths must not contain backslashes.");
1651
- for (const segment of pathname.split("/")) {
1652
- let decoded;
1653
- try {
1654
- decoded = decodeURIComponent(segment);
1655
- } catch {
1656
- throw new TypeError("REST path has malformed percent-encoding.");
1657
- }
1658
- const bare = decoded.replace(/[\u0000-\u0020\u007f]/g, "");
1659
- if (bare === "." || bare === ".." || /[\\/]/.test(decoded) && decoded.split(/[\\/]/).some((s) => {
1660
- const part = s.replace(/[\u0000-\u0020\u007f]/g, "");
1661
- return part === "." || part === "..";
1662
- }))
1663
- throw new TypeError("REST paths must not contain '.' or '..' segments.");
1664
- }
1665
- }
1666
- var REQUEST_DATA_KEYS = new Set([
1667
- "body",
1668
- "files",
1669
- "query",
1670
- "headers",
1671
- "reason",
1672
- "auth",
1673
- "signal",
1674
- "maxQueueWait"
1675
- ]);
1676
- function isRequestData(value) {
1677
- if (value === null || typeof value !== "object")
1678
- return false;
1679
- if (Array.isArray(value))
1680
- return false;
1681
- if (typeof FormData !== "undefined" && value instanceof FormData)
1682
- return false;
1683
- if (value instanceof Blob || value instanceof Uint8Array || value instanceof ArrayBuffer)
1684
- return false;
1685
- const keys = Object.keys(value);
1686
- if (keys.length === 0)
1687
- return false;
1688
- return keys.every((key) => REQUEST_DATA_KEYS.has(key));
1689
- }
1690
- function createRetryPolicy(maxRetries = 2) {
1691
- if (!Number.isFinite(maxRetries) || maxRetries < 0)
1692
- throw new TypeError("maxRetries must be a non-negative finite number.");
1693
- return {
1694
- maxRetries: Math.floor(maxRetries),
1695
- shouldRetry(method, status) {
1696
- const normalized = method.toUpperCase();
1697
- const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
1698
- if (status === 0)
1699
- return idempotent;
1700
- return status === 429 || status >= 500 && status <= 599 && idempotent;
1701
- },
1702
- getDelay(attempt, retryAfter) {
1703
- if (retryAfter !== undefined)
1704
- return Math.max(0, retryAfter * 1000);
1705
- return Math.min(1e4, 500 * 2 ** attempt) + Math.random() * 250;
1706
- }
1707
- };
1708
- }
1709
-
1710
- class QueueWaitExceeded extends Error {
1711
- ms;
1712
- constructor(ms) {
1713
- super("maxQueueWait exceeded");
1714
- this.ms = ms;
1715
- }
1716
- }
1717
- function checkWait(ms) {
1718
- if (ms === undefined)
1719
- return;
1720
- if (!(ms >= 0) || !Number.isFinite(ms))
1721
- throw new RangeError("maxQueueWait must be a non-negative number.");
1722
- return ms;
1723
- }
1724
-
1725
- class REST {
1726
- #retryPolicy;
1727
- #token;
1728
- #fingerprint;
1729
- #telemetry;
1730
- #closing = new AbortController;
1731
- #inFlight = 0;
1732
- #idle;
1733
- #rejected;
1734
- #stopOnInvalidToken;
1735
- #maxQueueWait;
1736
- #invalid = new Array(10).fill(0);
1737
- #invalidMinute = 0;
1738
- #drained;
1739
- #limiter;
1740
- #scheduler;
1741
- #transport;
1742
- #decoder = new ResponseDecoder;
1743
- #hooks = {};
1744
- #concurrentBuckets;
1745
- constructor(options = {}) {
1746
- this.#token = options.token;
1747
- this.#fingerprint = options.token ? tokenFingerprint(options.token) : undefined;
1748
- this.#telemetry = options.telemetry;
1749
- this.#scheduler = new RequestScheduler({
1750
- maxQueued: options.maxQueued,
1751
- maxQueuedPerBucket: options.maxQueuedPerBucket
1752
- });
1753
- this.#retryPolicy = options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);
1754
- this.#transport = options.transport ?? new HttpTransport({
1755
- baseURL: options.baseURL,
1756
- allowInsecureBaseURL: options.allowInsecureBaseURL,
1757
- timeout: options.timeout
1758
- });
1759
- const store = options.store ?? new MemoryRateLimitStore;
1760
- this.#concurrentBuckets = (options.concurrentBuckets ?? true) && typeof store.reserve === "function";
1761
- this.#limiter = new RateLimiter(store, {
1762
- concurrent: this.#concurrentBuckets,
1763
- maxBackoff: options.maxBackoff,
1764
- probeMs: this.#transport.timeout > 0 ? this.#transport.timeout + 1000 : undefined,
1765
- telemetry: options.telemetry,
1766
- globalLimit: options.globalLimit === false ? undefined : options.globalLimit ?? 50
1767
- });
1768
- this.#stopOnInvalidToken = options.stopOnInvalidToken ?? true;
1769
- this.#maxQueueWait = checkWait(options.maxQueueWait);
1770
- if (options.hooks)
1771
- this.#hooks = options.hooks;
1772
- }
1773
- get store() {
1774
- return this.#limiter.store;
1775
- }
1776
- setHooks(hooks) {
1777
- this.#hooks = hooks;
1778
- return this;
1779
- }
1780
- setToken(token) {
1781
- if (!token.trim())
1782
- throw new TypeError("A Discord bot token is required.");
1783
- this.#token = token;
1784
- this.#fingerprint = tokenFingerprint(token);
1785
- this.#rejected = undefined;
1786
- }
1787
- diagnostics() {
1788
- const store = this.#limiter.store;
1789
- return {
1790
- mode: store.mode ?? (store instanceof MemoryRateLimitStore ? "local" : "distributed"),
1791
- queued: this.#scheduler.queued,
1792
- inFlight: this.#inFlight,
1793
- buckets: this.#scheduler.size,
1794
- closed: this.#closing.signal.aborted,
1795
- invalidRequests: this.#invalidCount(0),
1796
- tokenRejected: this.#rejected !== undefined && this.#rejected === this.#fingerprint
1797
- };
1798
- }
1799
- async close() {
1800
- if (!this.#closing.signal.aborted)
1801
- this.#closing.abort(new Error("The REST client was closed."));
1802
- if (this.#inFlight === 0)
1803
- return;
1804
- this.#drained ??= new Promise((resolve) => {
1805
- this.#idle = resolve;
1806
- });
1807
- await this.#drained;
1808
- }
1809
- #invalidCount(add) {
1810
- const minute = Math.floor(Date.now() / 60000);
1811
- const elapsed = Math.min(10, minute - this.#invalidMinute);
1812
- for (let i = 1;i <= elapsed; i++)
1813
- this.#invalid[(this.#invalidMinute + i) % 10] = 0;
1814
- if (elapsed > 0)
1815
- this.#invalidMinute = minute;
1816
- this.#invalid[minute % 10] += add;
1817
- return this.#invalid.reduce((a, b) => a + b, 0);
1818
- }
1819
- #queueChanged() {
1820
- emitTelemetry(this.#telemetry, {
1821
- type: "queue",
1822
- queued: this.#scheduler.queued,
1823
- inFlight: this.#inFlight
1824
- });
1825
- }
1826
- async request(methodOrOptions, pathArg, bodyArg, optionsArg = {}) {
1827
- let method;
1828
- let path;
1829
- let body = bodyArg;
1830
- let options = optionsArg;
1831
- if (typeof methodOrOptions === "object" && methodOrOptions !== null) {
1832
- const { method: m, path: p, fullRoute, ...rest } = methodOrOptions;
1833
- method = m;
1834
- path = p ?? fullRoute ?? "";
1835
- body = rest.body;
1836
- options = rest;
1837
- } else {
1838
- method = methodOrOptions;
1839
- path = pathArg;
1840
- }
1841
- if (typeof method !== "string" || !method.toUpperCase())
1842
- throw new TypeError("REST method is required.");
1843
- if (!path.startsWith("/"))
1844
- throw new TypeError("REST paths must start with '/'.");
1845
- assertSafePath(path);
1846
- const route = createRouteKey(method, path);
1847
- const files = options.files;
1848
- if (files && files.length > 0 && !(body instanceof FormData))
1849
- body = this.#fileForm(body, await Promise.all(files.map(async (file) => ({
1850
- ...file,
1851
- data: await file.data
1852
- }))));
1853
- const query = serializeQuery(options.query);
1854
- const requestPath = query ? `${path}${query}` : path;
1855
- if (this.#closing.signal.aborted)
1856
- throw abortError(path, this.#closing.signal.reason);
1857
- const identity = requestIdentity(path, options.auth !== false && this.#token ? this.#fingerprint : undefined);
1858
- if (identity.bucket === this.#fingerprint && this.#rejected === this.#fingerprint)
1859
- throw new RESTError("Discord rejected this bot token (401); request not sent. Call setToken() with a valid token.", 401, undefined, undefined, { method: route.method, path, kind: "authentication" });
1860
- const queueWait = checkWait(options.maxQueueWait) ?? this.#maxQueueWait;
1861
- let deadline;
1862
- const sources = [this.#closing.signal];
1863
- if (options.signal)
1864
- sources.push(options.signal);
1865
- if (queueWait !== undefined) {
1866
- const expired = new AbortController;
1867
- deadline = setTimeout(() => expired.abort(new QueueWaitExceeded(queueWait)), queueWait);
1868
- sources.push(expired.signal);
1869
- }
1870
- const waitSignal = sources.length === 1 ? sources[0] : AbortSignal.any(sources);
1871
- const state = { attempts: 0, status: 0 };
1872
- const started = performance.now();
1873
- this.#inFlight++;
1874
- this.#queueChanged();
1875
- try {
1876
- return await this.#scheduler.runResolved(() => this.#limiter.resolveBucketKey(route.route, route.major, identity.bucket), waitSignal, path, (bucketKey, release) => {
1877
- this.#queueChanged();
1878
- return this.#attempts(route, requestPath, body, options, { identity, waitSignal, state }, bucketKey, release);
1879
- });
1880
- } catch (caught) {
1881
- let error = caught;
1882
- if (error instanceof RESTError && error.kind === "aborted" && error.cause instanceof QueueWaitExceeded)
1883
- error = new RESTError(`The request waited longer than maxQueueWait (${error.cause.ms}ms) before it could be sent.`, 0, undefined, undefined, { method: route.method, path, kind: "overloaded" });
1884
- if (error instanceof RESTError) {
1885
- if (error.kind === "aborted" && state.attempts === 0)
1886
- emitTelemetry(this.#telemetry, {
1887
- type: "cancelled",
1888
- stage: "queued"
1889
- });
1890
- else if (error.kind === "overloaded")
1891
- emitTelemetry(this.#telemetry, { type: "overloaded" });
1892
- if (state.status === 0)
1893
- state.status = error.status;
1894
- }
1895
- throw error;
1896
- } finally {
1897
- clearTimeout(deadline);
1898
- emitTelemetry(this.#telemetry, {
1899
- type: "request",
1900
- method: route.method,
1901
- route: route.route,
1902
- status: state.status,
1903
- attempts: state.attempts,
1904
- durationMs: performance.now() - started,
1905
- ok: state.status >= 200 && state.status < 300
1906
- });
1907
- if (--this.#inFlight === 0 && this.#idle) {
1908
- this.#idle();
1909
- this.#idle = undefined;
1910
- this.#drained = undefined;
1911
- }
1912
- this.#queueChanged();
1913
- }
1914
- }
1915
- async#attempts(route, requestPath, body, options, context, initialBucketKey, release) {
1916
- const { method, path } = route;
1917
- const { identity, waitSignal, state } = context;
1918
- const label = route.route;
1919
- let bucketKey = initialBucketKey;
1920
- for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
1921
- let grant;
1922
- try {
1923
- grant = await this.#limiter.acquire(bucketKey, waitSignal, path, { global: identity.global });
1924
- } catch (error) {
1925
- if (error instanceof RESTError && error.kind === "aborted")
1926
- emitTelemetry(this.#telemetry, {
1927
- type: "cancelled",
1928
- stage: "waiting"
1929
- });
1930
- throw error;
1931
- }
1932
- if (this.#concurrentBuckets && grant.known)
1933
- release();
1934
- if (waitSignal.aborted) {
1935
- await this.#limiter.releaseGrant(bucketKey, grant, true);
1936
- emitTelemetry(this.#telemetry, {
1937
- type: "cancelled",
1938
- stage: "waiting"
1939
- });
1940
- throw abortError(path, waitSignal.reason);
1941
- }
1942
- this.#emit(this.#hooks.onRequest, { method, path, attempt });
1943
- state.attempts = attempt + 1;
1944
- const requestStart = Date.now();
1945
- let response;
1946
- try {
1947
- response = await this.#transport.send({
1948
- method,
1949
- path: requestPath,
1950
- headers: this.#headers(body, options),
1951
- body: this.#encodeBody(body),
1952
- signal: options.signal
1953
- });
1954
- } catch (error) {
1955
- const notSent = error instanceof RESTError && error.kind === "aborted";
1956
- await this.#limiter.releaseGrant(bucketKey, grant, notSent);
1957
- if (error instanceof RESTError) {
1958
- if (error.kind === "aborted")
1959
- emitTelemetry(this.#telemetry, {
1960
- type: "cancelled",
1961
- stage: "transport"
1962
- });
1963
- throw error;
1964
- }
1965
- if (options.signal?.aborted) {
1966
- emitTelemetry(this.#telemetry, {
1967
- type: "cancelled",
1968
- stage: "transport"
1969
- });
1970
- throw abortError(path, options.signal.reason);
1971
- }
1972
- const failure = error instanceof TransportError ? error : new TransportError("Discord REST request failed", false, sanitizeCause(error));
1973
- emitTelemetry(this.#telemetry, {
1974
- type: "networkError",
1975
- timedOut: failure.timedOut
1976
- });
1977
- if (!failure.timedOut && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, 0)) {
1978
- const delay = this.#retryPolicy.getDelay(attempt);
1979
- this.#emit(this.#hooks.onRetry, {
1980
- method,
1981
- path,
1982
- attempt,
1983
- status: 0,
1984
- delayMs: delay
1985
- });
1986
- emitTelemetry(this.#telemetry, {
1987
- type: "retry",
1988
- route: label,
1989
- reason: "network",
1990
- attempt
1991
- });
1992
- await this.#retrySleep(delay, waitSignal, path);
1993
- continue;
1994
- }
1995
- throw new RESTError(failure.message, 0, undefined, undefined, {
1996
- method,
1997
- path,
1998
- cause: failure.cause ?? failure,
1999
- kind: failure.timedOut ? "timeout" : "network"
2000
- });
2001
- }
2002
- state.status = response.status;
2003
- if (response.status === 401 || response.status === 403 || response.status === 429 && response.headers.get("X-RateLimit-Scope") !== "shared")
2004
- this.#invalidCount(1);
2005
- if (response.status === 401 && this.#stopOnInvalidToken && identity.bucket === this.#fingerprint) {
2006
- this.#rejected = this.#fingerprint;
2007
- emitTelemetry(this.#telemetry, { type: "tokenRejected" });
2008
- }
2009
- const durationMs = Date.now() - requestStart;
2010
- this.#emit(this.#hooks.onResponse, {
2011
- method,
2012
- path,
2013
- status: response.status,
2014
- durationMs
2015
- });
2016
- emitTelemetry(this.#telemetry, {
2017
- type: "response",
2018
- method,
2019
- route: label,
2020
- status: response.status,
2021
- durationMs
2022
- });
2023
- const payload = await this.#decoder.read(response);
2024
- const data = response.ok ? {} : this.#decoder.errorData(payload);
2025
- bucketKey = await this.#limiter.applyResponse(response, bucketKey, route.route, route.major, {
2026
- identity: identity.bucket,
2027
- global: identity.global,
2028
- body: data,
2029
- grant
2030
- });
2031
- if (response.ok)
2032
- return response.status === 204 ? undefined : payload;
2033
- const limited = response.status === 429;
2034
- const retryAfterMs = limited ? this.#limiter.retryAfterMs(response, data) : undefined;
2035
- if (limited) {
2036
- const global = data.global === true || response.headers.get("X-RateLimit-Global") === "true";
2037
- const scope = response.headers.get("X-RateLimit-Scope")?.toLowerCase();
2038
- emitTelemetry(this.#telemetry, {
2039
- type: "rateLimited",
2040
- route: label,
2041
- scope: global ? "global" : scope === "user" || scope === "shared" ? scope : "unknown",
2042
- retryAfterMs
2043
- });
2044
- this.#emit(this.#hooks.onRateLimit, {
2045
- method,
2046
- path,
2047
- retryAfterMs,
2048
- global,
2049
- bucket: response.headers.get("X-RateLimit-Bucket") ?? await this.#limiter.store.getBucketHash(route.route)
2050
- });
2051
- }
2052
- if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, response.status)) {
2053
- const delay = this.#retryPolicy.getDelay(attempt, limited ? retryAfterMs / 1000 : undefined);
2054
- if (delay > this.#limiter.maxBackoff)
2055
- throw new RESTError(`Discord asked to wait ${Math.ceil(delay / 1000)}s before retrying, longer than maxBackoff (${Math.ceil(this.#limiter.maxBackoff / 1000)}s).`, response.status, data.code, payload, { method, path });
2056
- this.#emit(this.#hooks.onRetry, {
2057
- method,
2058
- path,
2059
- attempt,
2060
- status: response.status,
2061
- delayMs: delay
2062
- });
2063
- emitTelemetry(this.#telemetry, {
2064
- type: "retry",
2065
- route: label,
2066
- reason: limited ? "rateLimited" : "server",
2067
- attempt
2068
- });
2069
- await this.#retrySleep(limited ? delay - retryAfterMs : delay, waitSignal, path);
2070
- continue;
2071
- }
2072
- throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method, path });
2073
- }
2074
- throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method, path });
2075
- }
2076
- async#retrySleep(delay, signal, path) {
2077
- try {
2078
- await sleep(delay, signal, path);
2079
- } catch (error) {
2080
- emitTelemetry(this.#telemetry, {
2081
- type: "cancelled",
2082
- stage: "retry"
2083
- });
2084
- throw error;
2085
- }
2086
- }
2087
- #headers(body, options) {
2088
- const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
2089
- const headers = { ...options.headers };
2090
- const set = (name, value) => {
2091
- for (const key of Object.keys(headers))
2092
- if (key.toLowerCase() === name.toLowerCase())
2093
- delete headers[key];
2094
- headers[name] = value;
2095
- };
2096
- if (options.auth !== false && this.#token)
2097
- set("Authorization", `Bot ${this.#token}`);
2098
- set("User-Agent", USER_AGENT);
2099
- if (options.reason !== undefined)
2100
- set("X-Audit-Log-Reason", encodeURIComponent(options.reason));
2101
- if (!isFormData)
2102
- set("Content-Type", "application/json");
2103
- return headers;
2104
- }
2105
- #encodeBody(body) {
2106
- if (typeof FormData !== "undefined" && body instanceof FormData)
2107
- return body;
2108
- return body === undefined ? undefined : JSON.stringify(body);
2109
- }
2110
- #dispatch(method, path, bodyOrOptions, options) {
2111
- if (options === undefined && isRequestData(bodyOrOptions))
2112
- return this.request(method, path, bodyOrOptions.body, bodyOrOptions);
2113
- return this.request(method, path, bodyOrOptions, options);
2114
- }
2115
- get(path, options) {
2116
- return this.request("GET", path, options?.body, options);
2117
- }
2118
- post(path, body, options) {
2119
- return this.#dispatch("POST", path, body, options);
2120
- }
2121
- patch(path, body, options) {
2122
- return this.#dispatch("PATCH", path, body, options);
2123
- }
2124
- put(path, body, options) {
2125
- return this.#dispatch("PUT", path, body, options);
2126
- }
2127
- delete(path, options) {
2128
- return this.request("DELETE", path, options?.body, options);
2129
- }
2130
- #fileForm(payload, files) {
2131
- const form = new FormData;
2132
- form.append("payload_json", JSON.stringify(payload));
2133
- for (let i = 0;i < files.length; i++) {
2134
- const file = files[i];
2135
- const blob = file.data instanceof Blob ? file.data : new Blob([file.data], {
2136
- type: file.contentType ?? "application/octet-stream"
2137
- });
2138
- form.append(`files[${i}]`, blob, file.name.replace(/[\r\n"\\]/g, "_"));
2139
- }
2140
- return form;
2141
- }
2142
- postWithFiles(path, payload, files, options) {
2143
- return this.request("POST", path, payload, { ...options, files });
2144
- }
2145
- patchWithFiles(path, payload, files, options) {
2146
- return this.request("PATCH", path, payload, { ...options, files });
2147
- }
2148
- #emit(hook, ctx) {
2149
- if (!hook)
2150
- return;
2151
- const withPath = ctx;
2152
- if (typeof withPath.path === "string")
2153
- ctx = { ...ctx, path: redactPath(withPath.path) };
2154
- Promise.resolve().then(() => hook(ctx)).catch(() => {});
2155
- }
2156
- }
2
+ import {
3
+ MemoryRateLimitStore2,
4
+ redactPath2,
5
+ RESTError2,
6
+ abortError2,
7
+ normalizeRoutePath2,
8
+ majorParameter2,
9
+ scopeBucket2,
10
+ createRouteKey2,
11
+ serializeQuery2,
12
+ RequestScheduler2,
13
+ headerNumber2,
14
+ parseRateLimitHeaders2,
15
+ RESTMetrics2,
16
+ DEFAULT_MAX_BACKOFF_MS2,
17
+ RateLimiter2,
18
+ tokenFingerprint2,
19
+ requestIdentity2,
20
+ TransportError2,
21
+ HttpTransport2,
22
+ ResponseDecoder2,
23
+ Routes2,
24
+ RESTErrorCode2,
25
+ fromBunRedis2,
26
+ RedisRateLimitStore2,
27
+ WebhookClient2,
28
+ createRetryPolicy2,
29
+ REST2
30
+ } from "../chunks/index-7fp2hvmg.js";
2157
31
  export {
2158
- DEFAULT_MAX_BACKOFF_MS,
2159
- RESTError as DiscordAPIError,
2160
- HttpTransport,
2161
- MemoryRateLimitStore,
2162
- REST,
2163
- RESTError,
2164
- RESTErrorCode,
2165
- RESTMetrics,
2166
- RateLimiter,
2167
- RedisRateLimitStore,
2168
- RequestScheduler,
2169
- ResponseDecoder,
2170
- Routes,
2171
- TransportError,
2172
- WebhookClient,
2173
- abortError,
2174
- createRetryPolicy,
2175
- createRouteKey,
2176
- fromBunRedis,
2177
- headerNumber,
2178
- majorParameter,
2179
- normalizeRoutePath,
2180
- parseRateLimitHeaders,
2181
- redactPath,
2182
- requestIdentity,
2183
- scopeBucket,
2184
- serializeQuery,
2185
- tokenFingerprint
32
+ DEFAULT_MAX_BACKOFF_MS2 as DEFAULT_MAX_BACKOFF_MS,
33
+ HttpTransport2 as HttpTransport,
34
+ MemoryRateLimitStore2 as MemoryRateLimitStore,
35
+ REST2 as REST,
36
+ RESTError2 as RESTError,
37
+ RESTErrorCode2 as RESTErrorCode,
38
+ RESTMetrics2 as RESTMetrics,
39
+ RateLimiter2 as RateLimiter,
40
+ RedisRateLimitStore2 as RedisRateLimitStore,
41
+ RequestScheduler2 as RequestScheduler,
42
+ ResponseDecoder2 as ResponseDecoder,
43
+ Routes2 as Routes,
44
+ TransportError2 as TransportError,
45
+ WebhookClient2 as WebhookClient,
46
+ abortError2 as abortError,
47
+ createRetryPolicy2 as createRetryPolicy,
48
+ createRouteKey2 as createRouteKey,
49
+ fromBunRedis2 as fromBunRedis,
50
+ headerNumber2 as headerNumber,
51
+ majorParameter2 as majorParameter,
52
+ normalizeRoutePath2 as normalizeRoutePath,
53
+ parseRateLimitHeaders2 as parseRateLimitHeaders,
54
+ redactPath2 as redactPath,
55
+ requestIdentity2 as requestIdentity,
56
+ scopeBucket2 as scopeBucket,
57
+ serializeQuery2 as serializeQuery,
58
+ tokenFingerprint2 as tokenFingerprint
2186
59
  };
2187
60
 
2188
- //# debugId=94C16174A190B15864756E2164756E21
61
+ //# debugId=74F3F0A0834E8AED64756E2164756E21