lunibee 0.1.7 → 0.1.8

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 (71) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +223 -223
  3. package/dist/builders/commands.d.ts +6 -4
  4. package/dist/builders/components.d.ts +18 -0
  5. package/dist/builders/index.d.ts +2 -2
  6. package/dist/builders/index.js +49 -13
  7. package/dist/builders/index.js.map +9 -8
  8. package/dist/collection/index.js +2 -1
  9. package/dist/collection/index.js.map +4 -4
  10. package/dist/core/events.d.ts +86 -2
  11. package/dist/core/index.d.ts +10 -87
  12. package/dist/core/index.js +1752 -725
  13. package/dist/core/index.js.map +44 -24
  14. package/dist/formatters/index.js.map +1 -1
  15. package/dist/handlers/index.js.map +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +2103 -800
  18. package/dist/index.js.map +63 -43
  19. package/dist/managers/guild-resources.d.ts +109 -0
  20. package/dist/managers/guild.d.ts +5 -0
  21. package/dist/managers/index.d.ts +7 -1
  22. package/dist/managers/index.js +330 -12
  23. package/dist/managers/index.js.map +25 -21
  24. package/dist/rest/decoder.d.ts +23 -0
  25. package/dist/rest/errors.d.ts +20 -0
  26. package/dist/rest/index.d.ts +21 -17
  27. package/dist/rest/index.js +479 -263
  28. package/dist/rest/index.js.map +14 -8
  29. package/dist/rest/limiter.d.ts +42 -0
  30. package/dist/rest/redis.d.ts +18 -1
  31. package/dist/rest/route.d.ts +31 -0
  32. package/dist/rest/routes.d.ts +2 -0
  33. package/dist/rest/scheduler.d.ts +42 -0
  34. package/dist/rest/store.d.ts +29 -0
  35. package/dist/rest/transport.d.ts +40 -0
  36. package/dist/sharding/bus.d.ts +31 -0
  37. package/dist/sharding/cluster.d.ts +10 -0
  38. package/dist/sharding/index.d.ts +10 -2
  39. package/dist/sharding/index.js +1011 -394
  40. package/dist/sharding/index.js.map +18 -8
  41. package/dist/structures/base.d.ts +8 -0
  42. package/dist/structures/channels.d.ts +56 -0
  43. package/dist/structures/index.d.ts +1 -0
  44. package/dist/structures/index.js +211 -271
  45. package/dist/structures/index.js.map +17 -14
  46. package/dist/structures/interactions.d.ts +12 -52
  47. package/dist/structures/options.d.ts +55 -0
  48. package/dist/structures/resources.d.ts +3 -3
  49. package/dist/types/gateway-events.d.ts +132 -0
  50. package/dist/types/gateway.d.ts +181 -0
  51. package/dist/types/index.d.ts +2 -263
  52. package/dist/types/index.js +44 -2
  53. package/dist/types/index.js.map +5 -4
  54. package/dist/utils/index.js.map +1 -1
  55. package/dist/voice/index.d.ts +29 -1
  56. package/dist/voice/index.js +59 -7
  57. package/dist/voice/index.js.map +3 -3
  58. package/dist/ws/close-codes.d.ts +23 -0
  59. package/dist/ws/decoder.d.ts +58 -0
  60. package/dist/ws/heartbeat.d.ts +87 -0
  61. package/dist/ws/index.d.ts +18 -65
  62. package/dist/ws/index.js +903 -385
  63. package/dist/ws/index.js.map +15 -5
  64. package/dist/ws/opcodes.d.ts +14 -0
  65. package/dist/ws/protocol.d.ts +110 -0
  66. package/dist/ws/reconnect.d.ts +108 -0
  67. package/dist/ws/send-budget.d.ts +17 -0
  68. package/dist/ws/session.d.ts +100 -0
  69. package/dist/ws/state.d.ts +27 -0
  70. package/dist/ws/transport.d.ts +80 -0
  71. package/package.json +21 -20
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // @bun
2
2
  // packages/core/src/permissions.ts
3
3
  var PermissionOverwriteType;
4
- ((PermissionOverwriteType2) => {
5
- PermissionOverwriteType2[PermissionOverwriteType2["Role"] = 0] = "Role";
6
- PermissionOverwriteType2[PermissionOverwriteType2["Member"] = 1] = "Member";
4
+ ((PermissionOverwriteType) => {
5
+ PermissionOverwriteType[PermissionOverwriteType["Role"] = 0] = "Role";
6
+ PermissionOverwriteType[PermissionOverwriteType["Member"] = 1] = "Member";
7
7
  })(PermissionOverwriteType ||= {});
8
8
  var Permission = {
9
9
  createInstantInvite: 1n << 0n,
@@ -421,71 +421,71 @@ class PermissionsBitField extends PermissionSet {
421
421
  }
422
422
  // packages/core/src/events.ts
423
423
  var ClientEvent;
424
- ((ClientEvent2) => {
425
- ClientEvent2["Ready"] = "ready";
426
- ClientEvent2["Resumed"] = "resumed";
427
- ClientEvent2["InvalidSession"] = "invalidSession";
428
- ClientEvent2["Raw"] = "raw";
429
- ClientEvent2["Error"] = "error";
430
- ClientEvent2["Open"] = "open";
431
- ClientEvent2["Close"] = "close";
432
- ClientEvent2["MessageCreate"] = "messageCreate";
433
- ClientEvent2["MessageUpdate"] = "messageUpdate";
434
- ClientEvent2["MessageDelete"] = "messageDelete";
435
- ClientEvent2["MessageDeleteBulk"] = "messageDeleteBulk";
436
- ClientEvent2["MessageReactionAdd"] = "messageReactionAdd";
437
- ClientEvent2["MessageReactionRemove"] = "messageReactionRemove";
438
- ClientEvent2["MessageReactionRemoveAll"] = "messageReactionRemoveAll";
439
- ClientEvent2["MessageReactionRemoveEmoji"] = "messageReactionRemoveEmoji";
440
- ClientEvent2["MessagePollVoteAdd"] = "messagePollVoteAdd";
441
- ClientEvent2["MessagePollVoteRemove"] = "messagePollVoteRemove";
442
- ClientEvent2["GuildCreate"] = "guildCreate";
443
- ClientEvent2["GuildUpdate"] = "guildUpdate";
444
- ClientEvent2["GuildDelete"] = "guildDelete";
445
- ClientEvent2["GuildAvailable"] = "guildAvailable";
446
- ClientEvent2["GuildUnavailable"] = "guildUnavailable";
447
- ClientEvent2["GuildMemberAdd"] = "guildMemberAdd";
448
- ClientEvent2["GuildMemberUpdate"] = "guildMemberUpdate";
449
- ClientEvent2["GuildMemberRemove"] = "guildMemberRemove";
450
- ClientEvent2["GuildMembersChunk"] = "guildMembersChunk";
451
- ClientEvent2["GuildBanAdd"] = "guildBanAdd";
452
- ClientEvent2["GuildBanRemove"] = "guildBanRemove";
453
- ClientEvent2["GuildRoleCreate"] = "guildRoleCreate";
454
- ClientEvent2["GuildRoleUpdate"] = "guildRoleUpdate";
455
- ClientEvent2["GuildRoleDelete"] = "guildRoleDelete";
456
- ClientEvent2["GuildEmojisUpdate"] = "guildEmojisUpdate";
457
- ClientEvent2["GuildStickersUpdate"] = "guildStickersUpdate";
458
- ClientEvent2["GuildIntegrationsUpdate"] = "guildIntegrationsUpdate";
459
- ClientEvent2["GuildScheduledEventCreate"] = "guildScheduledEventCreate";
460
- ClientEvent2["GuildScheduledEventUpdate"] = "guildScheduledEventUpdate";
461
- ClientEvent2["GuildScheduledEventDelete"] = "guildScheduledEventDelete";
462
- ClientEvent2["GuildScheduledEventUserAdd"] = "guildScheduledEventUserAdd";
463
- ClientEvent2["GuildScheduledEventUserRemove"] = "guildScheduledEventUserRemove";
464
- ClientEvent2["AutoModerationRuleCreate"] = "autoModerationRuleCreate";
465
- ClientEvent2["AutoModerationRuleUpdate"] = "autoModerationRuleUpdate";
466
- ClientEvent2["AutoModerationRuleDelete"] = "autoModerationRuleDelete";
467
- ClientEvent2["AutoModerationActionExecution"] = "autoModerationActionExecution";
468
- ClientEvent2["ChannelCreate"] = "channelCreate";
469
- ClientEvent2["ChannelUpdate"] = "channelUpdate";
470
- ClientEvent2["ChannelDelete"] = "channelDelete";
471
- ClientEvent2["ChannelPinsUpdate"] = "channelPinsUpdate";
472
- ClientEvent2["ThreadCreate"] = "threadCreate";
473
- ClientEvent2["ThreadUpdate"] = "threadUpdate";
474
- ClientEvent2["ThreadDelete"] = "threadDelete";
475
- ClientEvent2["ThreadListSync"] = "threadListSync";
476
- ClientEvent2["ThreadMembersUpdate"] = "threadMembersUpdate";
477
- ClientEvent2["ThreadMemberUpdate"] = "threadMemberUpdate";
478
- ClientEvent2["StageInstanceCreate"] = "stageInstanceCreate";
479
- ClientEvent2["StageInstanceUpdate"] = "stageInstanceUpdate";
480
- ClientEvent2["StageInstanceDelete"] = "stageInstanceDelete";
481
- ClientEvent2["InviteCreate"] = "inviteCreate";
482
- ClientEvent2["InviteDelete"] = "inviteDelete";
483
- ClientEvent2["WebhooksUpdate"] = "webhooksUpdate";
484
- ClientEvent2["VoiceStateUpdate"] = "voiceStateUpdate";
485
- ClientEvent2["VoiceServerUpdate"] = "voiceServerUpdate";
486
- ClientEvent2["PresenceUpdate"] = "presenceUpdate";
487
- ClientEvent2["TypingStart"] = "typingStart";
488
- ClientEvent2["InteractionCreate"] = "interactionCreate";
424
+ ((ClientEvent) => {
425
+ ClientEvent["Ready"] = "ready";
426
+ ClientEvent["Resumed"] = "resumed";
427
+ ClientEvent["InvalidSession"] = "invalidSession";
428
+ ClientEvent["Raw"] = "raw";
429
+ ClientEvent["Error"] = "error";
430
+ ClientEvent["Open"] = "open";
431
+ ClientEvent["Close"] = "close";
432
+ ClientEvent["MessageCreate"] = "messageCreate";
433
+ ClientEvent["MessageUpdate"] = "messageUpdate";
434
+ ClientEvent["MessageDelete"] = "messageDelete";
435
+ ClientEvent["MessageDeleteBulk"] = "messageDeleteBulk";
436
+ ClientEvent["MessageReactionAdd"] = "messageReactionAdd";
437
+ ClientEvent["MessageReactionRemove"] = "messageReactionRemove";
438
+ ClientEvent["MessageReactionRemoveAll"] = "messageReactionRemoveAll";
439
+ ClientEvent["MessageReactionRemoveEmoji"] = "messageReactionRemoveEmoji";
440
+ ClientEvent["MessagePollVoteAdd"] = "messagePollVoteAdd";
441
+ ClientEvent["MessagePollVoteRemove"] = "messagePollVoteRemove";
442
+ ClientEvent["GuildCreate"] = "guildCreate";
443
+ ClientEvent["GuildUpdate"] = "guildUpdate";
444
+ ClientEvent["GuildDelete"] = "guildDelete";
445
+ ClientEvent["GuildAvailable"] = "guildAvailable";
446
+ ClientEvent["GuildUnavailable"] = "guildUnavailable";
447
+ ClientEvent["GuildMemberAdd"] = "guildMemberAdd";
448
+ ClientEvent["GuildMemberUpdate"] = "guildMemberUpdate";
449
+ ClientEvent["GuildMemberRemove"] = "guildMemberRemove";
450
+ ClientEvent["GuildMembersChunk"] = "guildMembersChunk";
451
+ ClientEvent["GuildBanAdd"] = "guildBanAdd";
452
+ ClientEvent["GuildBanRemove"] = "guildBanRemove";
453
+ ClientEvent["GuildRoleCreate"] = "guildRoleCreate";
454
+ ClientEvent["GuildRoleUpdate"] = "guildRoleUpdate";
455
+ ClientEvent["GuildRoleDelete"] = "guildRoleDelete";
456
+ ClientEvent["GuildEmojisUpdate"] = "guildEmojisUpdate";
457
+ ClientEvent["GuildStickersUpdate"] = "guildStickersUpdate";
458
+ ClientEvent["GuildIntegrationsUpdate"] = "guildIntegrationsUpdate";
459
+ ClientEvent["GuildScheduledEventCreate"] = "guildScheduledEventCreate";
460
+ ClientEvent["GuildScheduledEventUpdate"] = "guildScheduledEventUpdate";
461
+ ClientEvent["GuildScheduledEventDelete"] = "guildScheduledEventDelete";
462
+ ClientEvent["GuildScheduledEventUserAdd"] = "guildScheduledEventUserAdd";
463
+ ClientEvent["GuildScheduledEventUserRemove"] = "guildScheduledEventUserRemove";
464
+ ClientEvent["AutoModerationRuleCreate"] = "autoModerationRuleCreate";
465
+ ClientEvent["AutoModerationRuleUpdate"] = "autoModerationRuleUpdate";
466
+ ClientEvent["AutoModerationRuleDelete"] = "autoModerationRuleDelete";
467
+ ClientEvent["AutoModerationActionExecution"] = "autoModerationActionExecution";
468
+ ClientEvent["ChannelCreate"] = "channelCreate";
469
+ ClientEvent["ChannelUpdate"] = "channelUpdate";
470
+ ClientEvent["ChannelDelete"] = "channelDelete";
471
+ ClientEvent["ChannelPinsUpdate"] = "channelPinsUpdate";
472
+ ClientEvent["ThreadCreate"] = "threadCreate";
473
+ ClientEvent["ThreadUpdate"] = "threadUpdate";
474
+ ClientEvent["ThreadDelete"] = "threadDelete";
475
+ ClientEvent["ThreadListSync"] = "threadListSync";
476
+ ClientEvent["ThreadMembersUpdate"] = "threadMembersUpdate";
477
+ ClientEvent["ThreadMemberUpdate"] = "threadMemberUpdate";
478
+ ClientEvent["StageInstanceCreate"] = "stageInstanceCreate";
479
+ ClientEvent["StageInstanceUpdate"] = "stageInstanceUpdate";
480
+ ClientEvent["StageInstanceDelete"] = "stageInstanceDelete";
481
+ ClientEvent["InviteCreate"] = "inviteCreate";
482
+ ClientEvent["InviteDelete"] = "inviteDelete";
483
+ ClientEvent["WebhooksUpdate"] = "webhooksUpdate";
484
+ ClientEvent["VoiceStateUpdate"] = "voiceStateUpdate";
485
+ ClientEvent["VoiceServerUpdate"] = "voiceServerUpdate";
486
+ ClientEvent["PresenceUpdate"] = "presenceUpdate";
487
+ ClientEvent["TypingStart"] = "typingStart";
488
+ ClientEvent["InteractionCreate"] = "interactionCreate";
489
489
  })(ClientEvent ||= {});
490
490
  // packages/core/src/collector.ts
491
491
  import { EventEmitter } from "events";
@@ -548,16 +548,20 @@ class Collector extends EventEmitter {
548
548
  if (this.ended) {
549
549
  return reject(new Error(`Collector already ended: ${this.endReason ?? "unknown"}`));
550
550
  }
551
- this.once("collect", (item) => {
551
+ const onCollect = (item) => {
552
+ this.off("end", onEnd);
552
553
  resolve(item);
553
- });
554
- this.once("end", (_collected, reason) => {
554
+ };
555
+ const onEnd = (_collected, reason) => {
556
+ this.off("collect", onCollect);
555
557
  reject(new Error(`Collector ended before item was collected: ${reason}`));
556
- });
558
+ };
559
+ this.once("collect", onCollect);
560
+ this.once("end", onEnd);
557
561
  });
558
562
  }
559
563
  }
560
- // packages/types/src/index.ts
564
+ // packages/types/src/gateway.ts
561
565
  var GatewayIntentBits = {
562
566
  Guilds: 1 << 0,
563
567
  GuildMembers: 1 << 1,
@@ -613,6 +617,8 @@ var Intents = IntentBits;
613
617
  function resolveGatewayIntents(intents) {
614
618
  if (typeof intents === "number")
615
619
  return intents;
620
+ if (intents && typeof intents === "object" && !Array.isArray(intents))
621
+ return intents.bitfield;
616
622
  if (Array.isArray(intents)) {
617
623
  return intents.reduce((acc, intent) => {
618
624
  return acc | resolveGatewayIntents(intent);
@@ -632,6 +638,45 @@ function resolveGatewayIntents(intents) {
632
638
  }
633
639
  return 0;
634
640
  }
641
+
642
+ class IntentsBitField {
643
+ static Flags = GatewayIntentBits;
644
+ bitfield;
645
+ constructor(bits = 0) {
646
+ this.bitfield = resolveGatewayIntents(bits);
647
+ }
648
+ has(bits) {
649
+ const resolved = resolveGatewayIntents(bits);
650
+ return (this.bitfield & resolved) === resolved;
651
+ }
652
+ any(bits) {
653
+ return (this.bitfield & resolveGatewayIntents(bits)) !== 0;
654
+ }
655
+ add(...bits) {
656
+ for (const bit of bits)
657
+ this.bitfield |= resolveGatewayIntents(bit);
658
+ return this;
659
+ }
660
+ remove(...bits) {
661
+ for (const bit of bits)
662
+ this.bitfield &= ~resolveGatewayIntents(bit);
663
+ return this;
664
+ }
665
+ toArray() {
666
+ return Object.keys(GatewayIntentBits).filter((name) => this.has(GatewayIntentBits[name]));
667
+ }
668
+ equals(bits) {
669
+ return this.bitfield === resolveGatewayIntents(bits);
670
+ }
671
+ valueOf() {
672
+ return this.bitfield;
673
+ }
674
+ toJSON() {
675
+ return this.bitfield;
676
+ }
677
+ }
678
+
679
+ // packages/types/src/index.ts
635
680
  var MessageFlags = {
636
681
  Crossposted: 1,
637
682
  IsCrosspost: 2,
@@ -734,6 +779,7 @@ class Cache {
734
779
  if (!Number.isFinite(interval) || interval < 1)
735
780
  throw new RangeError("Cache sweepInterval must be positive.");
736
781
  this.#timer = setInterval(() => this.sweep(), interval);
782
+ this.#timer.unref?.();
737
783
  }
738
784
  }
739
785
  get size() {
@@ -1097,7 +1143,7 @@ class ResourceManager extends Manager {
1097
1143
  // packages/rest/package.json
1098
1144
  var package_default = {
1099
1145
  name: "@lunibee/rest",
1100
- version: "0.1.7",
1146
+ version: "0.1.8",
1101
1147
  type: "module",
1102
1148
  module: "./src/index.ts",
1103
1149
  types: "./src/index.ts",
@@ -1122,6 +1168,17 @@ class MemoryRateLimitStore {
1122
1168
  getBucket(key) {
1123
1169
  return this.#buckets.get(key);
1124
1170
  }
1171
+ reserve(key) {
1172
+ const state = this.#buckets.get(key);
1173
+ const now = Date.now();
1174
+ if (!state || state.resetAt <= now)
1175
+ return { granted: true };
1176
+ if (state.remaining > 0) {
1177
+ state.remaining -= 1;
1178
+ return { granted: true };
1179
+ }
1180
+ return { granted: false, retryAfterMs: state.resetAt - now };
1181
+ }
1125
1182
  updateBucket(key, state) {
1126
1183
  this.#buckets.set(key, state);
1127
1184
  const now = Date.now();
@@ -1145,6 +1202,301 @@ class MemoryRateLimitStore {
1145
1202
  }
1146
1203
  }
1147
1204
 
1205
+ // packages/rest/src/errors.ts
1206
+ class RESTError extends Error {
1207
+ status;
1208
+ code;
1209
+ errors;
1210
+ method;
1211
+ path;
1212
+ constructor(message, status, code, errors, options = {}) {
1213
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
1214
+ this.name = "RESTError";
1215
+ this.status = status;
1216
+ this.code = code;
1217
+ this.errors = errors;
1218
+ this.method = options.method;
1219
+ this.path = options.path;
1220
+ }
1221
+ }
1222
+ function abortError(path, reason) {
1223
+ return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
1224
+ }
1225
+ async function sleep(delay, signal, path) {
1226
+ if (delay <= 0)
1227
+ return;
1228
+ await new Promise((resolve, reject) => {
1229
+ const timer = setTimeout(resolve, delay);
1230
+ const abort = () => {
1231
+ clearTimeout(timer);
1232
+ reject(abortError(path, signal?.reason));
1233
+ };
1234
+ if (signal?.aborted)
1235
+ return abort();
1236
+ signal?.addEventListener("abort", abort, { once: true });
1237
+ });
1238
+ }
1239
+ async function abortable(promise, signal, path) {
1240
+ if (!signal)
1241
+ return promise;
1242
+ if (signal.aborted)
1243
+ throw abortError(path, signal.reason);
1244
+ return Promise.race([
1245
+ promise,
1246
+ new Promise((_, reject) => signal.addEventListener("abort", () => reject(abortError(path, signal.reason)), { once: true }))
1247
+ ]);
1248
+ }
1249
+
1250
+ // packages/rest/src/route.ts
1251
+ function normalizeRoutePath(path) {
1252
+ const queryIndex = path.indexOf("?");
1253
+ const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
1254
+ return routePath.replace(/\/\d+(?=\/|$)/g, "/:id");
1255
+ }
1256
+ function majorParameter(path) {
1257
+ const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/.exec(path);
1258
+ if (!match)
1259
+ return "@none";
1260
+ if (match[1] === "webhooks" && match[3] !== undefined)
1261
+ return `${match[2]}:${match[3]}`;
1262
+ return match[2];
1263
+ }
1264
+ function scopeBucket(hashOrRoute, major) {
1265
+ return `${hashOrRoute}|${major}`;
1266
+ }
1267
+ function createRouteKey(method, path) {
1268
+ const normalizedMethod = method.toUpperCase();
1269
+ return {
1270
+ method: normalizedMethod,
1271
+ path,
1272
+ route: `${normalizedMethod}:${normalizeRoutePath(path)}`,
1273
+ major: majorParameter(path)
1274
+ };
1275
+ }
1276
+ function serializeQuery(query) {
1277
+ if (query === undefined)
1278
+ return "";
1279
+ if (typeof query === "string")
1280
+ return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
1281
+ const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
1282
+ const serialized = params.toString();
1283
+ return serialized.length === 0 ? "" : `?${serialized}`;
1284
+ }
1285
+
1286
+ // packages/rest/src/scheduler.ts
1287
+ class RequestScheduler {
1288
+ static MAX_REMAPS = 2;
1289
+ #queues = new Map;
1290
+ get size() {
1291
+ return this.#queues.size;
1292
+ }
1293
+ async runResolved(resolveKey, signal, path, task) {
1294
+ let key = await resolveKey();
1295
+ for (let hop = 0;hop < RequestScheduler.MAX_REMAPS; hop++) {
1296
+ const outcome = await this.run(key, signal, path, async (release) => {
1297
+ const current = await resolveKey();
1298
+ if (current !== key)
1299
+ return { done: false, key: current };
1300
+ return { done: true, value: await task(key, release) };
1301
+ });
1302
+ if (outcome.done)
1303
+ return outcome.value;
1304
+ key = outcome.key;
1305
+ }
1306
+ return this.run(key, signal, path, (release) => task(key, release));
1307
+ }
1308
+ async run(key, signal, path, task) {
1309
+ const previous = this.#queues.get(key) ?? Promise.resolve();
1310
+ let open;
1311
+ const gate = new Promise((resolve) => {
1312
+ open = resolve;
1313
+ });
1314
+ this.#queues.set(key, gate);
1315
+ let released = false;
1316
+ const release = () => {
1317
+ if (released)
1318
+ return;
1319
+ released = true;
1320
+ open();
1321
+ if (this.#queues.get(key) === gate)
1322
+ this.#queues.delete(key);
1323
+ };
1324
+ try {
1325
+ await abortable(previous, signal, path);
1326
+ return await task(release);
1327
+ } finally {
1328
+ release();
1329
+ }
1330
+ }
1331
+ }
1332
+
1333
+ // packages/rest/src/limiter.ts
1334
+ class RateLimiter {
1335
+ #store;
1336
+ #concurrent;
1337
+ constructor(store, options = {}) {
1338
+ this.#store = store;
1339
+ this.#concurrent = options.concurrent ?? false;
1340
+ }
1341
+ get store() {
1342
+ return this.#store;
1343
+ }
1344
+ async resolveBucketKey(route, major) {
1345
+ return scopeBucket(await this.#store.getBucketHash(route) ?? route, major);
1346
+ }
1347
+ get reserves() {
1348
+ return typeof this.#store.reserve === "function";
1349
+ }
1350
+ async acquire(bucketKey, signal, path) {
1351
+ await this.#waitGlobal(signal, path);
1352
+ if (!this.#store.reserve) {
1353
+ const bucket = await this.#store.getBucket(bucketKey);
1354
+ if (bucket) {
1355
+ const delay = bucket.resetAt - Date.now();
1356
+ if (bucket.remaining <= 0 && delay > 0)
1357
+ await sleep(delay, signal, path);
1358
+ }
1359
+ return;
1360
+ }
1361
+ for (let attempt = 0;attempt < 8; attempt++) {
1362
+ const reservation = await this.#store.reserve(bucketKey);
1363
+ if (reservation.granted)
1364
+ return;
1365
+ await sleep(Math.max(1, reservation.retryAfterMs), signal, path);
1366
+ await this.#waitGlobal(signal, path);
1367
+ }
1368
+ }
1369
+ async#waitGlobal(signal, path) {
1370
+ const globalDelay = await this.#store.getGlobalReset() - Date.now();
1371
+ if (globalDelay > 0)
1372
+ await sleep(globalDelay, signal, path);
1373
+ }
1374
+ async noteGlobalReset(resetAt) {
1375
+ const current = await this.#store.getGlobalReset();
1376
+ await this.#store.setGlobalReset(Math.max(current, resetAt));
1377
+ }
1378
+ async applyResponse(response, bucketKey, route, major) {
1379
+ let currentKey = bucketKey;
1380
+ const serverBucket = response.headers.get("X-RateLimit-Bucket");
1381
+ if (serverBucket) {
1382
+ await this.#store.setBucketHash(route, serverBucket);
1383
+ currentKey = scopeBucket(serverBucket, major);
1384
+ }
1385
+ const stored = await this.#store.getBucket(currentKey);
1386
+ const bucket = stored ? { ...stored } : { remaining: 1, resetAt: 0 };
1387
+ const windowActive = this.#concurrent && !!stored && stored.resetAt > Date.now();
1388
+ const remaining = Number(response.headers.get("X-RateLimit-Remaining"));
1389
+ const resetAfter = Number(response.headers.get("X-RateLimit-Reset-After"));
1390
+ const reset = Number(response.headers.get("X-RateLimit-Reset"));
1391
+ if (Number.isFinite(remaining))
1392
+ bucket.remaining = Math.max(0, remaining);
1393
+ if (Number.isFinite(resetAfter))
1394
+ bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;
1395
+ else if (Number.isFinite(reset))
1396
+ bucket.resetAt = reset * 1000;
1397
+ if (windowActive && stored) {
1398
+ bucket.remaining = Math.min(bucket.remaining, stored.remaining);
1399
+ bucket.resetAt = Math.max(bucket.resetAt, stored.resetAt);
1400
+ }
1401
+ if (response.status === 429) {
1402
+ const retryAfter = Number(response.headers.get("Retry-After"));
1403
+ if (Number.isFinite(retryAfter))
1404
+ bucket.resetAt = Math.max(bucket.resetAt, Date.now() + retryAfter * 1000);
1405
+ }
1406
+ await this.#store.updateBucket(currentKey, bucket);
1407
+ if (response.headers.get("X-RateLimit-Global") === "true") {
1408
+ const retryAfter = Number(response.headers.get("Retry-After"));
1409
+ if (Number.isFinite(retryAfter))
1410
+ await this.noteGlobalReset(Date.now() + retryAfter * 1000);
1411
+ }
1412
+ return currentKey;
1413
+ }
1414
+ }
1415
+
1416
+ // packages/rest/src/transport.ts
1417
+ class TransportError extends Error {
1418
+ timedOut;
1419
+ constructor(message, timedOut, cause) {
1420
+ super(message, cause === undefined ? undefined : { cause });
1421
+ this.name = "TransportError";
1422
+ this.timedOut = timedOut;
1423
+ }
1424
+ }
1425
+
1426
+ class HttpTransport {
1427
+ #baseURL;
1428
+ #timeout;
1429
+ #fetch;
1430
+ constructor(options = {}) {
1431
+ this.#baseURL = (options.baseURL ?? "https://discord.com/api/v10").replace(/\/$/, "");
1432
+ this.#timeout = Math.max(1, options.timeout ?? 15000);
1433
+ this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
1434
+ }
1435
+ get baseURL() {
1436
+ return this.#baseURL;
1437
+ }
1438
+ get timeout() {
1439
+ return this.#timeout;
1440
+ }
1441
+ async send(request) {
1442
+ if (request.signal?.aborted)
1443
+ throw abortError(request.path, request.signal.reason);
1444
+ const controller = new AbortController;
1445
+ const onAbort = () => controller.abort(request.signal?.reason);
1446
+ request.signal?.addEventListener("abort", onAbort, { once: true });
1447
+ const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
1448
+ try {
1449
+ return await this.#fetch(`${this.#baseURL}${request.path}`, {
1450
+ method: request.method,
1451
+ headers: request.headers,
1452
+ body: request.body,
1453
+ signal: controller.signal
1454
+ });
1455
+ } catch (error) {
1456
+ if (error instanceof RESTError)
1457
+ throw error;
1458
+ const timedOut = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
1459
+ throw new TransportError(timedOut ? `Discord REST request timed out after ${this.#timeout}ms` : "Discord REST request failed", timedOut, error);
1460
+ } finally {
1461
+ clearTimeout(timer);
1462
+ request.signal?.removeEventListener("abort", onAbort);
1463
+ }
1464
+ }
1465
+ }
1466
+
1467
+ // packages/rest/src/decoder.ts
1468
+ class ResponseDecoder {
1469
+ async read(response) {
1470
+ if (response.status === 204)
1471
+ return;
1472
+ const contentType = response.headers.get("content-type") ?? "";
1473
+ if (contentType.includes("application/json"))
1474
+ return response.json().catch(() => {
1475
+ return;
1476
+ });
1477
+ return response.text().catch(() => {
1478
+ return;
1479
+ });
1480
+ }
1481
+ errorData(payload) {
1482
+ if (!payload || typeof payload !== "object")
1483
+ return {};
1484
+ const data = payload;
1485
+ return {
1486
+ message: typeof data.message === "string" ? data.message : undefined,
1487
+ code: typeof data.code === "number" ? data.code : undefined,
1488
+ retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
1489
+ global: data.global === true
1490
+ };
1491
+ }
1492
+ retryAfter(response, data) {
1493
+ const raw = response.headers.get("Retry-After");
1494
+ const header = raw === null ? Number.NaN : Number(raw);
1495
+ const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
1496
+ return Math.max(0, retryAfter);
1497
+ }
1498
+ }
1499
+
1148
1500
  // packages/rest/src/routes.ts
1149
1501
  var Routes = {
1150
1502
  user: () => "/users/@me",
@@ -1186,6 +1538,8 @@ var Routes = {
1186
1538
  threadMembers: (threadId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members`,
1187
1539
  guildScheduledEvents: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events`,
1188
1540
  guildScheduledEvent: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}`,
1541
+ guildScheduledEventUsers: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}/users`,
1542
+ channelPermission: (channelId, overwriteId) => `/channels/${snowflake(channelId, "Channel ID")}/permissions/${snowflake(overwriteId, "Overwrite ID")}`,
1189
1543
  guildAutoModerationRules: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules`,
1190
1544
  guildAutoModerationRule: (guildId, ruleId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules/${snowflake(ruleId, "Rule ID")}`,
1191
1545
  voiceRegions: () => "/voice/regions",
@@ -1209,15 +1563,36 @@ function snowflake(value, field) {
1209
1563
  return value;
1210
1564
  }
1211
1565
  // packages/rest/src/redis.ts
1566
+ var RESERVE_SCRIPT = `
1567
+ local raw = redis.call('GET', KEYS[1])
1568
+ if not raw then return {1, 0} end
1569
+ local ok, state = pcall(cjson.decode, raw)
1570
+ if not ok or type(state) ~= 'table' or type(state.resetAt) ~= 'number' then
1571
+ return {1, 0}
1572
+ end
1573
+ local now = tonumber(ARGV[1])
1574
+ local wait = state.resetAt - now
1575
+ if wait <= 0 then return {1, 0} end
1576
+ if type(state.remaining) == 'number' and state.remaining > 0 then
1577
+ state.remaining = state.remaining - 1
1578
+ redis.call('SET', KEYS[1], cjson.encode(state), 'PX', math.ceil(wait))
1579
+ return {1, 0}
1580
+ end
1581
+ return {0, math.ceil(wait)}
1582
+ `;
1583
+
1212
1584
  class RedisRateLimitStore {
1213
1585
  #client;
1214
1586
  #prefix;
1215
1587
  #onErrorCallback;
1588
+ #local = new MemoryRateLimitStore;
1216
1589
  #lastError;
1217
1590
  constructor(options) {
1218
1591
  this.#client = options.client;
1219
1592
  this.#prefix = options.prefix ?? "lunibee:rest:";
1220
1593
  this.#onErrorCallback = options.onError;
1594
+ if (typeof options.client.eval === "function")
1595
+ this.reserve = (key) => this.#reserve(key);
1221
1596
  }
1222
1597
  get lastError() {
1223
1598
  return this.#lastError;
@@ -1242,10 +1617,11 @@ class RedisRateLimitStore {
1242
1617
  return hash ?? undefined;
1243
1618
  } catch (error) {
1244
1619
  this.#onError("getBucketHash", error);
1245
- return;
1620
+ return this.#local.getBucketHash(route);
1246
1621
  }
1247
1622
  }
1248
1623
  async setBucketHash(route, hash) {
1624
+ this.#local.setBucketHash(route, hash);
1249
1625
  try {
1250
1626
  await this.#client.set(`${this.#prefix}route:${route}`, hash, "EX", 604800);
1251
1627
  this.#markHealthy();
@@ -1262,10 +1638,11 @@ class RedisRateLimitStore {
1262
1638
  return JSON.parse(data);
1263
1639
  } catch (error) {
1264
1640
  this.#onError("getBucket", error);
1265
- return;
1641
+ return this.#local.getBucket(key);
1266
1642
  }
1267
1643
  }
1268
1644
  async updateBucket(key, state) {
1645
+ this.#local.updateBucket(key, state);
1269
1646
  const bucketKey = `${this.#prefix}bucket:${key}`;
1270
1647
  const ttl = Math.ceil((state.resetAt - Date.now()) / 1000);
1271
1648
  try {
@@ -1280,6 +1657,23 @@ class RedisRateLimitStore {
1280
1657
  this.#onError("updateBucket", error);
1281
1658
  }
1282
1659
  }
1660
+ get supportsReservation() {
1661
+ return typeof this.#client.eval === "function";
1662
+ }
1663
+ reserve;
1664
+ async#reserve(key) {
1665
+ try {
1666
+ const result = await this.#client.eval(RESERVE_SCRIPT, 1, `${this.#prefix}bucket:${key}`, Date.now());
1667
+ this.#markHealthy();
1668
+ if (!Array.isArray(result))
1669
+ return { granted: true };
1670
+ const [granted, retryAfterMs] = result;
1671
+ return Number(granted) === 1 ? { granted: true } : { granted: false, retryAfterMs: Number(retryAfterMs) || 0 };
1672
+ } catch (error) {
1673
+ this.#onError("reserve", error);
1674
+ return this.#local.reserve(key);
1675
+ }
1676
+ }
1283
1677
  async getGlobalReset() {
1284
1678
  try {
1285
1679
  const resetAt = await this.#client.get(`${this.#prefix}global`);
@@ -1287,10 +1681,11 @@ class RedisRateLimitStore {
1287
1681
  return resetAt ? Number(resetAt) : 0;
1288
1682
  } catch (error) {
1289
1683
  this.#onError("getGlobalReset", error);
1290
- return 0;
1684
+ return this.#local.getGlobalReset();
1291
1685
  }
1292
1686
  }
1293
1687
  async setGlobalReset(resetAt) {
1688
+ this.#local.setGlobalReset(resetAt);
1294
1689
  const globalKey = `${this.#prefix}global`;
1295
1690
  const ttl = Math.ceil((resetAt - Date.now()) / 1000);
1296
1691
  try {
@@ -1346,22 +1741,6 @@ class WebhookClient {
1346
1741
  }
1347
1742
 
1348
1743
  // packages/rest/src/index.ts
1349
- class RESTError extends Error {
1350
- status;
1351
- code;
1352
- errors;
1353
- method;
1354
- path;
1355
- constructor(message, status, code, errors, options = {}) {
1356
- super(message, options.cause === undefined ? undefined : { cause: options.cause });
1357
- this.name = "RESTError";
1358
- this.status = status;
1359
- this.code = code;
1360
- this.errors = errors;
1361
- this.method = options.method;
1362
- this.path = options.path;
1363
- }
1364
- }
1365
1744
  var LIBRARY_VERSION = package_default.version;
1366
1745
  var LIBRARY_URL = "https://github.com/Ekretos/lunibee";
1367
1746
  var USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${package_default.version})`;
@@ -1388,26 +1767,6 @@ function isRequestData(value) {
1388
1767
  return false;
1389
1768
  return keys.every((key) => REQUEST_DATA_KEYS.has(key));
1390
1769
  }
1391
- function serializeQuery(query) {
1392
- if (query === undefined)
1393
- return "";
1394
- if (typeof query === "string")
1395
- return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
1396
- const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
1397
- const serialized = params.toString();
1398
- return serialized.length === 0 ? "" : `?${serialized}`;
1399
- }
1400
- function majorParameter(path) {
1401
- const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/.exec(path);
1402
- if (!match)
1403
- return "@none";
1404
- if (match[1] === "webhooks" && match[3] !== undefined)
1405
- return `${match[2]}:${match[3]}`;
1406
- return match[2];
1407
- }
1408
- function scopeBucket(hashOrRoute, major) {
1409
- return `${hashOrRoute}|${major}`;
1410
- }
1411
1770
  function createRetryPolicy(maxRetries = 2) {
1412
1771
  if (!Number.isFinite(maxRetries) || maxRetries < 0)
1413
1772
  throw new TypeError("maxRetries must be a non-negative finite number.");
@@ -1415,7 +1774,10 @@ function createRetryPolicy(maxRetries = 2) {
1415
1774
  maxRetries: Math.floor(maxRetries),
1416
1775
  shouldRetry(method, status) {
1417
1776
  const normalized = method.toUpperCase();
1418
- return status === 429 || status >= 500 && status <= 599 && ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
1777
+ const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
1778
+ if (status === 0)
1779
+ return idempotent;
1780
+ return status === 429 || status >= 500 && status <= 599 && idempotent;
1419
1781
  },
1420
1782
  getDelay(attempt, retryAfter) {
1421
1783
  if (retryAfter !== undefined)
@@ -1426,22 +1788,32 @@ function createRetryPolicy(maxRetries = 2) {
1426
1788
  }
1427
1789
 
1428
1790
  class REST {
1429
- #baseURL;
1430
- #timeout;
1431
1791
  #retryPolicy;
1432
1792
  #token;
1433
- #store;
1434
- #localQueues = new Map;
1793
+ #limiter;
1794
+ #scheduler = new RequestScheduler;
1795
+ #transport;
1796
+ #decoder = new ResponseDecoder;
1435
1797
  #hooks = {};
1798
+ #concurrentBuckets;
1436
1799
  constructor(options = {}) {
1437
1800
  this.#token = options.token;
1438
- this.#timeout = Math.max(1, options.timeout ?? 15000);
1439
1801
  this.#retryPolicy = options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);
1440
- this.#baseURL = (options.baseURL ?? "https://discord.com/api/v10").replace(/\/$/, "");
1441
- this.#store = options.store ?? new MemoryRateLimitStore;
1802
+ this.#transport = options.transport ?? new HttpTransport({
1803
+ baseURL: options.baseURL,
1804
+ timeout: options.timeout
1805
+ });
1806
+ const store = options.store ?? new MemoryRateLimitStore;
1807
+ this.#concurrentBuckets = (options.concurrentBuckets ?? false) && typeof store.reserve === "function";
1808
+ this.#limiter = new RateLimiter(store, {
1809
+ concurrent: this.#concurrentBuckets
1810
+ });
1442
1811
  if (options.hooks)
1443
1812
  this.#hooks = options.hooks;
1444
1813
  }
1814
+ get store() {
1815
+ return this.#limiter.store;
1816
+ }
1445
1817
  setHooks(hooks) {
1446
1818
  this.#hooks = hooks;
1447
1819
  return this;
@@ -1452,121 +1824,109 @@ class REST {
1452
1824
  this.#token = token;
1453
1825
  }
1454
1826
  async request(method, path, body, options = {}) {
1455
- const normalizedMethod = method.toUpperCase();
1456
- if (!normalizedMethod)
1827
+ if (!method.toUpperCase())
1457
1828
  throw new TypeError("REST method is required.");
1458
1829
  if (!path.startsWith("/"))
1459
1830
  throw new TypeError("REST paths must start with '/'.");
1831
+ const route = createRouteKey(method, path);
1460
1832
  const files = options.files;
1461
1833
  if (files && files.length > 0 && !(body instanceof FormData))
1462
1834
  body = this.#fileForm(body, files);
1463
1835
  const query = serializeQuery(options.query);
1464
- const finalPath = query ? `${path}${query}` : path;
1465
- const route = `${normalizedMethod}:${this.#normalizeRoute(path)}`;
1466
- const major = majorParameter(path);
1467
- let bucketKey = scopeBucket(await this.#store.getBucketHash(route) ?? route, major);
1468
- const queueKey = bucketKey;
1469
- const previous = this.#localQueues.get(queueKey) ?? Promise.resolve();
1470
- let release;
1471
- const gate = new Promise((resolve) => {
1472
- release = resolve;
1473
- });
1474
- this.#localQueues.set(queueKey, gate);
1475
- await this.#abortable(previous, options.signal, path);
1476
- try {
1477
- for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
1478
- await this.#wait(bucketKey, options.signal, path);
1479
- await this.#waitGlobal(options.signal, path);
1480
- const controller = new AbortController;
1481
- const onAbort = () => controller.abort(options.signal?.reason);
1482
- if (options.signal?.aborted)
1483
- throw this.#abortError(path, options.signal.reason);
1484
- options.signal?.addEventListener("abort", onAbort, {
1485
- once: true
1836
+ const requestPath = query ? `${path}${query}` : path;
1837
+ return this.#scheduler.runResolved(() => this.#limiter.resolveBucketKey(route.route, route.major), options.signal, path, (bucketKey, release) => this.#attempts(route, requestPath, body, options, bucketKey, release));
1838
+ }
1839
+ async#attempts(route, requestPath, body, options, initialBucketKey, release) {
1840
+ const { method, path } = route;
1841
+ let bucketKey = initialBucketKey;
1842
+ for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
1843
+ await this.#limiter.acquire(bucketKey, options.signal, path);
1844
+ if (this.#concurrentBuckets) {
1845
+ const bucket = await this.#limiter.store.getBucket(bucketKey);
1846
+ if (bucket && bucket.resetAt > Date.now())
1847
+ release();
1848
+ }
1849
+ this.#emit(this.#hooks.onRequest, { method, path, attempt });
1850
+ const requestStart = Date.now();
1851
+ let response;
1852
+ try {
1853
+ response = await this.#transport.send({
1854
+ method,
1855
+ path: requestPath,
1856
+ headers: this.#headers(body, options),
1857
+ body: this.#encodeBody(body),
1858
+ signal: options.signal
1486
1859
  });
1487
- const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
1488
- const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
1489
- const headers = { ...options.headers };
1490
- if (options.auth !== false && this.#token)
1491
- headers["Authorization"] = `Bot ${this.#token}`;
1492
- headers["User-Agent"] = USER_AGENT;
1493
- if (options.reason !== undefined)
1494
- headers["X-Audit-Log-Reason"] = encodeURIComponent(options.reason);
1495
- if (!isFormData)
1496
- headers["Content-Type"] = "application/json";
1497
- const fetchBody = isFormData ? body : body === undefined ? undefined : JSON.stringify(body);
1498
- try {
1499
- this.#emit(this.#hooks.onRequest, {
1500
- method: normalizedMethod,
1501
- path,
1502
- attempt
1503
- });
1504
- const requestStart = Date.now();
1505
- const response = await fetch(`${this.#baseURL}${finalPath}`, {
1506
- method: normalizedMethod,
1507
- headers,
1508
- body: fetchBody,
1509
- signal: controller.signal
1510
- });
1511
- this.#emit(this.#hooks.onResponse, {
1512
- method: normalizedMethod,
1513
- path,
1514
- status: response.status,
1515
- durationMs: Date.now() - requestStart
1516
- });
1517
- bucketKey = await this.#update(response, bucketKey, route, major);
1518
- const payload = await this.#readPayload(response);
1519
- if (response.ok)
1520
- return response.status === 204 ? undefined : payload;
1521
- const data = this.#errorData(payload);
1522
- const retryAfter = response.status === 429 ? this.#retryAfter(response, data) : undefined;
1523
- if (data.global && retryAfter !== undefined) {
1524
- const currentGlobal = await this.#store.getGlobalReset();
1525
- await this.#store.setGlobalReset(Math.max(currentGlobal, Date.now() + retryAfter * 1000));
1526
- }
1527
- if (response.status === 429)
1528
- this.#emit(this.#hooks.onRateLimit, {
1529
- method: normalizedMethod,
1530
- path,
1531
- retryAfterMs: (retryAfter ?? 1) * 1000,
1532
- global: data.global ?? false,
1533
- bucket: await this.#store.getBucketHash(route)
1534
- });
1535
- if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, response.status)) {
1536
- const delay = this.#retryPolicy.getDelay(attempt, retryAfter);
1537
- this.#emit(this.#hooks.onRetry, {
1538
- method: normalizedMethod,
1539
- path,
1540
- attempt,
1541
- status: response.status,
1542
- delayMs: delay
1543
- });
1544
- await this.#sleep(delay, options.signal, path);
1545
- continue;
1546
- }
1547
- throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method: normalizedMethod, path });
1548
- } catch (error) {
1549
- if (error instanceof RESTError)
1550
- throw error;
1551
- if (options.signal?.aborted)
1552
- throw this.#abortError(path, options.signal.reason);
1553
- const isTimeout = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
1554
- if (!isTimeout && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, 0)) {
1555
- await this.#sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
1556
- continue;
1557
- }
1558
- throw new RESTError(isTimeout ? `Discord REST request timed out after ${this.#timeout}ms` : "Discord REST request failed", 0, undefined, undefined, { method: normalizedMethod, path, cause: error });
1559
- } finally {
1560
- clearTimeout(timer);
1561
- options.signal?.removeEventListener("abort", onAbort);
1860
+ } catch (error) {
1861
+ if (error instanceof RESTError)
1862
+ throw error;
1863
+ if (options.signal?.aborted)
1864
+ throw abortError(path, options.signal.reason);
1865
+ const failure = error instanceof TransportError ? error : new TransportError("Discord REST request failed", false, error);
1866
+ if (!failure.timedOut && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, 0)) {
1867
+ await sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
1868
+ continue;
1562
1869
  }
1870
+ throw new RESTError(failure.message, 0, undefined, undefined, {
1871
+ method,
1872
+ path,
1873
+ cause: failure.cause ?? failure
1874
+ });
1563
1875
  }
1564
- throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method: normalizedMethod, path });
1565
- } finally {
1566
- release();
1567
- if (this.#localQueues.get(queueKey) === gate)
1568
- this.#localQueues.delete(queueKey);
1569
- }
1876
+ this.#emit(this.#hooks.onResponse, {
1877
+ method,
1878
+ path,
1879
+ status: response.status,
1880
+ durationMs: Date.now() - requestStart
1881
+ });
1882
+ bucketKey = await this.#limiter.applyResponse(response, bucketKey, route.route, route.major);
1883
+ const payload = await this.#decoder.read(response);
1884
+ if (response.ok)
1885
+ return response.status === 204 ? undefined : payload;
1886
+ const data = this.#decoder.errorData(payload);
1887
+ const retryAfter = response.status === 429 ? this.#decoder.retryAfter(response, data) : undefined;
1888
+ if (data.global && retryAfter !== undefined)
1889
+ await this.#limiter.noteGlobalReset(Date.now() + retryAfter * 1000);
1890
+ if (response.status === 429)
1891
+ this.#emit(this.#hooks.onRateLimit, {
1892
+ method,
1893
+ path,
1894
+ retryAfterMs: (retryAfter ?? 1) * 1000,
1895
+ global: data.global ?? false,
1896
+ bucket: await this.#limiter.store.getBucketHash(route.route)
1897
+ });
1898
+ if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, response.status)) {
1899
+ const delay = this.#retryPolicy.getDelay(attempt, retryAfter);
1900
+ this.#emit(this.#hooks.onRetry, {
1901
+ method,
1902
+ path,
1903
+ attempt,
1904
+ status: response.status,
1905
+ delayMs: delay
1906
+ });
1907
+ await sleep(delay, options.signal, path);
1908
+ continue;
1909
+ }
1910
+ throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method, path });
1911
+ }
1912
+ throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method, path });
1913
+ }
1914
+ #headers(body, options) {
1915
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
1916
+ const headers = { ...options.headers };
1917
+ if (options.auth !== false && this.#token)
1918
+ headers["Authorization"] = `Bot ${this.#token}`;
1919
+ headers["User-Agent"] = USER_AGENT;
1920
+ if (options.reason !== undefined)
1921
+ headers["X-Audit-Log-Reason"] = encodeURIComponent(options.reason);
1922
+ if (!isFormData)
1923
+ headers["Content-Type"] = "application/json";
1924
+ return headers;
1925
+ }
1926
+ #encodeBody(body) {
1927
+ if (typeof FormData !== "undefined" && body instanceof FormData)
1928
+ return body;
1929
+ return body === undefined ? undefined : JSON.stringify(body);
1570
1930
  }
1571
1931
  #dispatch(method, path, bodyOrOptions, options) {
1572
1932
  if (options === undefined && isRequestData(bodyOrOptions))
@@ -1611,115 +1971,6 @@ class REST {
1611
1971
  return;
1612
1972
  Promise.resolve().then(() => hook(ctx)).catch(() => {});
1613
1973
  }
1614
- async#wait(bucketKey, signal, path) {
1615
- const bucket = await this.#store.getBucket(bucketKey);
1616
- if (!bucket)
1617
- return;
1618
- const delay = bucket.resetAt - Date.now();
1619
- if (bucket.remaining <= 0 && delay > 0)
1620
- await this.#sleep(delay, signal, path);
1621
- }
1622
- async#waitGlobal(signal, path) {
1623
- const globalResetAt = await this.#store.getGlobalReset();
1624
- const delay = globalResetAt - Date.now();
1625
- if (delay > 0)
1626
- await this.#sleep(delay, signal, path);
1627
- }
1628
- async#sleep(delay, signal, path) {
1629
- if (delay <= 0)
1630
- return;
1631
- await new Promise((resolve, reject) => {
1632
- const timer = setTimeout(resolve, delay);
1633
- const abort = () => {
1634
- clearTimeout(timer);
1635
- reject(this.#abortError(path, signal?.reason));
1636
- };
1637
- if (signal?.aborted)
1638
- return abort();
1639
- signal?.addEventListener("abort", abort, { once: true });
1640
- });
1641
- }
1642
- async#abortable(promise, signal, path) {
1643
- if (!signal)
1644
- return promise;
1645
- if (signal.aborted)
1646
- throw this.#abortError(path, signal.reason);
1647
- return Promise.race([
1648
- promise,
1649
- new Promise((_, reject) => signal.addEventListener("abort", () => reject(this.#abortError(path, signal.reason)), { once: true }))
1650
- ]);
1651
- }
1652
- #abortError(path, reason) {
1653
- return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
1654
- }
1655
- async#readPayload(response) {
1656
- if (response.status === 204)
1657
- return;
1658
- const contentType = response.headers.get("content-type") ?? "";
1659
- if (contentType.includes("application/json"))
1660
- return response.json().catch(() => {
1661
- return;
1662
- });
1663
- return response.text().catch(() => {
1664
- return;
1665
- });
1666
- }
1667
- #errorData(payload) {
1668
- if (!payload || typeof payload !== "object")
1669
- return {};
1670
- const data = payload;
1671
- return {
1672
- message: typeof data.message === "string" ? data.message : undefined,
1673
- code: typeof data.code === "number" ? data.code : undefined,
1674
- retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
1675
- global: data.global === true
1676
- };
1677
- }
1678
- #retryAfter(response, data) {
1679
- const header = Number(response.headers.get("Retry-After"));
1680
- const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
1681
- return Math.max(0, retryAfter);
1682
- }
1683
- #normalizeRoute(path) {
1684
- const queryIndex = path.indexOf("?");
1685
- const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
1686
- return routePath.replace(/\/\d+(?=\/|$)/g, "/:id");
1687
- }
1688
- async#update(response, bucketKey, route, major) {
1689
- let currentKey = bucketKey;
1690
- const serverBucket = response.headers.get("X-RateLimit-Bucket");
1691
- if (serverBucket) {
1692
- await this.#store.setBucketHash(route, serverBucket);
1693
- currentKey = scopeBucket(serverBucket, major);
1694
- }
1695
- let bucket = await this.#store.getBucket(currentKey) ?? {
1696
- remaining: 1,
1697
- resetAt: 0
1698
- };
1699
- const remaining = Number(response.headers.get("X-RateLimit-Remaining"));
1700
- const resetAfter = Number(response.headers.get("X-RateLimit-Reset-After"));
1701
- const reset = Number(response.headers.get("X-RateLimit-Reset"));
1702
- if (Number.isFinite(remaining))
1703
- bucket.remaining = Math.max(0, remaining);
1704
- if (Number.isFinite(resetAfter))
1705
- bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;
1706
- else if (Number.isFinite(reset))
1707
- bucket.resetAt = reset * 1000;
1708
- if (response.status === 429) {
1709
- const retryAfter = Number(response.headers.get("Retry-After"));
1710
- if (Number.isFinite(retryAfter))
1711
- bucket.resetAt = Math.max(bucket.resetAt, Date.now() + retryAfter * 1000);
1712
- }
1713
- await this.#store.updateBucket(currentKey, bucket);
1714
- if (response.headers.get("X-RateLimit-Global") === "true") {
1715
- const retryAfter = Number(response.headers.get("Retry-After"));
1716
- if (Number.isFinite(retryAfter)) {
1717
- const currentGlobal = await this.#store.getGlobalReset();
1718
- await this.#store.setGlobalReset(Math.max(currentGlobal, Date.now() + retryAfter * 1000));
1719
- }
1720
- }
1721
- return currentKey;
1722
- }
1723
1974
  }
1724
1975
 
1725
1976
  // packages/structures/src/base.ts
@@ -1841,6 +2092,18 @@ class Channel extends BaseStructure {
1841
2092
  update(options) {
1842
2093
  return this.edit(options);
1843
2094
  }
2095
+ isThread() {
2096
+ return this.type === 10 || this.type === 11 || this.type === 12;
2097
+ }
2098
+ isTextBased() {
2099
+ return [0, 1, 2, 3, 5, 10, 11, 12, 13].includes(this.type);
2100
+ }
2101
+ isVoiceBased() {
2102
+ return this.type === 2 || this.type === 13;
2103
+ }
2104
+ isDMBased() {
2105
+ return this.type === 1 || this.type === 3;
2106
+ }
1844
2107
  toString() {
1845
2108
  return `<#${this.id}>`;
1846
2109
  }
@@ -2137,7 +2400,9 @@ class Emoji extends BaseStructure {
2137
2400
  animated;
2138
2401
  available;
2139
2402
  constructor(data) {
2140
- super(data.id ?? "unicode");
2403
+ super(data.id ?? "0");
2404
+ if (data.id === null)
2405
+ this.id = "unicode";
2141
2406
  this.name = data.name;
2142
2407
  this.roleIds = data.roles ?? [];
2143
2408
  this.user = data.user ? new User(data.user) : null;
@@ -2270,14 +2535,107 @@ class GuildOnboarding extends BaseStructure {
2270
2535
  this.mode = data.mode;
2271
2536
  }
2272
2537
  }
2273
- // packages/structures/src/interactions.ts
2274
- var InteractionType = {
2275
- Ping: 1,
2276
- ApplicationCommand: 2,
2277
- MessageComponent: 3,
2278
- ApplicationCommandAutocomplete: 4,
2279
- ModalSubmit: 5
2280
- };
2538
+ // packages/structures/src/channels.ts
2539
+ class NewsChannel extends TextChannel {
2540
+ }
2541
+
2542
+ class DMChannel extends Channel {
2543
+ recipients;
2544
+ ownerId;
2545
+ constructor(data, context) {
2546
+ super(data, context);
2547
+ this.recipients = (data.recipients ?? []).map((u) => new User(u));
2548
+ this.ownerId = data.owner_id ?? null;
2549
+ }
2550
+ }
2551
+
2552
+ class VoiceChannel extends Channel {
2553
+ bitrate;
2554
+ userLimit;
2555
+ rtcRegion;
2556
+ videoQualityMode;
2557
+ constructor(data, context) {
2558
+ super(data, context);
2559
+ this.bitrate = data.bitrate ?? 64000;
2560
+ this.userLimit = data.user_limit ?? 0;
2561
+ this.rtcRegion = data.rtc_region ?? null;
2562
+ this.videoQualityMode = data.video_quality_mode ?? 1;
2563
+ }
2564
+ }
2565
+
2566
+ class StageChannel extends VoiceChannel {
2567
+ }
2568
+
2569
+ class CategoryChannel extends Channel {
2570
+ }
2571
+
2572
+ class ThreadChannel extends Channel {
2573
+ ownerId;
2574
+ messageCount;
2575
+ memberCount;
2576
+ metadata;
2577
+ constructor(data, context) {
2578
+ super(data, context);
2579
+ this.ownerId = data.owner_id ?? null;
2580
+ this.messageCount = data.message_count ?? 0;
2581
+ this.memberCount = data.member_count ?? 0;
2582
+ this.metadata = data.thread_metadata ?? null;
2583
+ }
2584
+ get archived() {
2585
+ return this.metadata?.archived ?? false;
2586
+ }
2587
+ get locked() {
2588
+ return this.metadata?.locked ?? false;
2589
+ }
2590
+ get autoArchiveDuration() {
2591
+ return this.metadata?.auto_archive_duration ?? null;
2592
+ }
2593
+ }
2594
+
2595
+ class ForumChannel extends Channel {
2596
+ availableTags;
2597
+ defaultReactionEmoji;
2598
+ defaultThreadRateLimitPerUser;
2599
+ defaultAutoArchiveDuration;
2600
+ constructor(data, context) {
2601
+ super(data, context);
2602
+ this.availableTags = data.available_tags ?? [];
2603
+ this.defaultReactionEmoji = data.default_reaction_emoji ?? null;
2604
+ this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user ?? 0;
2605
+ this.defaultAutoArchiveDuration = data.default_auto_archive_duration ?? null;
2606
+ }
2607
+ }
2608
+
2609
+ class MediaChannel extends ForumChannel {
2610
+ }
2611
+ function createChannel(data, context) {
2612
+ switch (data.type) {
2613
+ case 0:
2614
+ return new TextChannel(data, context);
2615
+ case 1:
2616
+ case 3:
2617
+ return new DMChannel(data, context);
2618
+ case 2:
2619
+ return new VoiceChannel(data, context);
2620
+ case 4:
2621
+ return new CategoryChannel(data, context);
2622
+ case 5:
2623
+ return new NewsChannel(data, context);
2624
+ case 10:
2625
+ case 11:
2626
+ case 12:
2627
+ return new ThreadChannel(data, context);
2628
+ case 13:
2629
+ return new StageChannel(data, context);
2630
+ case 15:
2631
+ return new ForumChannel(data, context);
2632
+ case 16:
2633
+ return new MediaChannel(data, context);
2634
+ default:
2635
+ return new Channel(data, context);
2636
+ }
2637
+ }
2638
+ // packages/structures/src/options.ts
2281
2639
  var OptionType = {
2282
2640
  SubCommand: 1,
2283
2641
  SubCommandGroup: 2,
@@ -2333,6 +2691,12 @@ class CommandOptions {
2333
2691
  }
2334
2692
  return this.#subcommandGroup;
2335
2693
  }
2694
+ getFocused(full = false) {
2695
+ const focused = this.#options.find((o) => o.focused);
2696
+ if (!focused)
2697
+ throw new TypeError("No focused option was provided.");
2698
+ return full ? focused : focused.value ?? "";
2699
+ }
2336
2700
  getString(name, required = false) {
2337
2701
  const opt = this.#get(name);
2338
2702
  if (!opt || opt.type !== OptionType.String) {
@@ -2438,6 +2802,14 @@ class CommandOptions {
2438
2802
  return this.#options;
2439
2803
  }
2440
2804
  }
2805
+ // packages/structures/src/interactions.ts
2806
+ var InteractionType = {
2807
+ Ping: 1,
2808
+ ApplicationCommand: 2,
2809
+ MessageComponent: 3,
2810
+ ApplicationCommandAutocomplete: 4,
2811
+ ModalSubmit: 5
2812
+ };
2441
2813
 
2442
2814
  class InteractionResponse {
2443
2815
  type;
@@ -2501,6 +2873,30 @@ class Interaction {
2501
2873
  isAutocomplete() {
2502
2874
  return this.type === InteractionType.ApplicationCommandAutocomplete;
2503
2875
  }
2876
+ #componentType() {
2877
+ return this.isMessageComponent() ? this.data.data?.component_type ?? 0 : 0;
2878
+ }
2879
+ isButton() {
2880
+ return this.#componentType() === 2;
2881
+ }
2882
+ isStringSelectMenu() {
2883
+ return this.#componentType() === 3;
2884
+ }
2885
+ isUserSelectMenu() {
2886
+ return this.#componentType() === 5;
2887
+ }
2888
+ isRoleSelectMenu() {
2889
+ return this.#componentType() === 6;
2890
+ }
2891
+ isMentionableSelectMenu() {
2892
+ return this.#componentType() === 7;
2893
+ }
2894
+ isChannelSelectMenu() {
2895
+ return this.#componentType() === 8;
2896
+ }
2897
+ isAnySelectMenu() {
2898
+ return [3, 5, 6, 7, 8].includes(this.#componentType());
2899
+ }
2504
2900
  assertUnacknowledged() {
2505
2901
  if (this.replied || this.deferred)
2506
2902
  throw new Error("Interaction has already been acknowledged.");
@@ -2611,6 +3007,11 @@ class ModalSubmitInteraction extends Interaction {
2611
3007
  }
2612
3008
 
2613
3009
  class AutocompleteInteraction extends Interaction {
3010
+ options;
3011
+ constructor(client, data) {
3012
+ super(client, data);
3013
+ this.options = new CommandOptions(data.data?.options ?? []);
3014
+ }
2614
3015
  get commandName() {
2615
3016
  return this.data?.data?.name ?? "";
2616
3017
  }
@@ -3034,7 +3435,7 @@ class ThreadManager {
3034
3435
  auto_archive_duration: options.autoArchiveDuration,
3035
3436
  rate_limit_per_user: options.rateLimitPerUser
3036
3437
  });
3037
- return new Channel(data, this.#context);
3438
+ return createChannel(data, this.#context);
3038
3439
  }
3039
3440
  }
3040
3441
 
@@ -3062,18 +3463,173 @@ class UserManager extends ResourceManager {
3062
3463
  });
3063
3464
  }
3064
3465
  }
3065
- // packages/managers/src/guild.ts
3466
+ // packages/managers/src/guild-resources.ts
3066
3467
  function withQuery(path, params) {
3468
+ const query = new URLSearchParams;
3469
+ for (const [key, value] of Object.entries(params))
3470
+ if (value !== undefined)
3471
+ query.set(key, String(value));
3472
+ const suffix = query.toString();
3473
+ return suffix ? `${path}?${suffix}` : path;
3474
+ }
3475
+
3476
+ class GuildBanManager extends Manager {
3477
+ #rest;
3478
+ guildId;
3479
+ constructor(rest, guildId) {
3480
+ super();
3481
+ this.#rest = rest;
3482
+ this.guildId = guildId;
3483
+ }
3484
+ async fetch(userId) {
3485
+ const ban = await this.#rest.get(Routes.guildBan(this.guildId, userId));
3486
+ this.set(ban.user.id, ban);
3487
+ return ban;
3488
+ }
3489
+ async fetchAll(options = {}) {
3490
+ const limit = options.limit === undefined ? undefined : Math.min(1000, Math.max(1, options.limit));
3491
+ const bans = await this.#rest.get(withQuery(Routes.guildBans(this.guildId), {
3492
+ limit,
3493
+ before: options.before,
3494
+ after: options.after
3495
+ }));
3496
+ for (const ban of bans)
3497
+ this.set(ban.user.id, ban);
3498
+ return bans;
3499
+ }
3500
+ async create(userId, options = {}) {
3501
+ await this.#rest.put(Routes.guildBan(this.guildId, userId), { delete_message_seconds: options.deleteMessageSeconds }, { reason: options.reason });
3502
+ }
3503
+ async remove(userId, reason) {
3504
+ await this.#rest.delete(Routes.guildBan(this.guildId, userId), {
3505
+ reason
3506
+ });
3507
+ this.delete(userId);
3508
+ }
3509
+ }
3510
+
3511
+ class GuildScheduledEventManager extends Manager {
3512
+ #rest;
3513
+ guildId;
3514
+ constructor(rest, guildId) {
3515
+ super();
3516
+ this.#rest = rest;
3517
+ this.guildId = guildId;
3518
+ }
3519
+ #store(event) {
3520
+ this.set(event.id, event);
3521
+ return event;
3522
+ }
3523
+ async fetch(eventId, options = {}) {
3524
+ return this.#store(await this.#rest.get(withQuery(Routes.guildScheduledEvent(this.guildId, eventId), {
3525
+ with_user_count: options.withUserCount
3526
+ })));
3527
+ }
3528
+ async fetchAll(options = {}) {
3529
+ const events = await this.#rest.get(withQuery(Routes.guildScheduledEvents(this.guildId), {
3530
+ with_user_count: options.withUserCount
3531
+ }));
3532
+ return events.map((event) => this.#store(event));
3533
+ }
3534
+ async create(options, reason) {
3535
+ return this.#store(await this.#rest.post(Routes.guildScheduledEvents(this.guildId), options, { reason }));
3536
+ }
3537
+ async edit(eventId, options, reason) {
3538
+ return this.#store(await this.#rest.patch(Routes.guildScheduledEvent(this.guildId, eventId), options, { reason }));
3539
+ }
3540
+ async remove(eventId) {
3541
+ await this.#rest.delete(Routes.guildScheduledEvent(this.guildId, eventId));
3542
+ this.delete(eventId);
3543
+ }
3544
+ async fetchSubscribers(eventId, options = {}) {
3545
+ const limit = options.limit === undefined ? undefined : Math.min(100, Math.max(1, options.limit));
3546
+ return this.#rest.get(withQuery(Routes.guildScheduledEventUsers(this.guildId, eventId), {
3547
+ limit,
3548
+ with_member: options.withMember,
3549
+ before: options.before,
3550
+ after: options.after
3551
+ }));
3552
+ }
3553
+ }
3554
+
3555
+ class StageInstanceManager extends Manager {
3556
+ #rest;
3557
+ constructor(rest) {
3558
+ super();
3559
+ this.#rest = rest;
3560
+ }
3561
+ #store(stage) {
3562
+ this.set(stage.channel_id, stage);
3563
+ return stage;
3564
+ }
3565
+ async create(channelId, options, reason) {
3566
+ return this.#store(await this.#rest.post(Routes.stageInstances(), {
3567
+ channel_id: channelId,
3568
+ topic: options.topic,
3569
+ privacy_level: options.privacyLevel,
3570
+ send_start_notification: options.sendStartNotification,
3571
+ guild_scheduled_event_id: options.guildScheduledEventId
3572
+ }, { reason }));
3573
+ }
3574
+ async fetch(channelId) {
3575
+ return this.#store(await this.#rest.get(Routes.stageInstanceByChannel(channelId)));
3576
+ }
3577
+ async edit(channelId, options, reason) {
3578
+ return this.#store(await this.#rest.patch(Routes.stageInstanceByChannel(channelId), { topic: options.topic, privacy_level: options.privacyLevel }, { reason }));
3579
+ }
3580
+ async remove(channelId, reason) {
3581
+ await this.#rest.delete(Routes.stageInstanceByChannel(channelId), {
3582
+ reason
3583
+ });
3584
+ this.delete(channelId);
3585
+ }
3586
+ }
3587
+
3588
+ class PermissionOverwriteManager {
3589
+ #rest;
3590
+ channelId;
3591
+ constructor(rest, channelId) {
3592
+ this.#rest = rest;
3593
+ this.channelId = channelId;
3594
+ }
3595
+ async edit(overwriteId, options, reason) {
3596
+ await this.#rest.put(Routes.channelPermission(this.channelId, overwriteId), {
3597
+ type: options.type,
3598
+ allow: BigInt(options.allow ?? 0).toString(),
3599
+ deny: BigInt(options.deny ?? 0).toString()
3600
+ }, { reason });
3601
+ }
3602
+ async remove(overwriteId, reason) {
3603
+ await this.#rest.delete(Routes.channelPermission(this.channelId, overwriteId), { reason });
3604
+ }
3605
+ }
3606
+
3607
+ // packages/managers/src/guild.ts
3608
+ function withQuery2(path, params) {
3067
3609
  const suffix = params.toString();
3068
3610
  return suffix ? `${path}?${suffix}` : path;
3069
3611
  }
3070
3612
 
3071
3613
  class GuildManager extends ResourceManager {
3072
3614
  #rest;
3615
+ #bans = new Map;
3616
+ #scheduledEvents = new Map;
3073
3617
  constructor(rest) {
3074
3618
  super(async (id) => new Guild(await rest.get(Routes.guild(id))), (guild) => guild.id);
3075
3619
  this.#rest = rest;
3076
3620
  }
3621
+ bans(guildId) {
3622
+ let manager = this.#bans.get(guildId);
3623
+ if (!manager)
3624
+ this.#bans.set(guildId, manager = new GuildBanManager(this.#rest, guildId));
3625
+ return manager;
3626
+ }
3627
+ scheduledEvents(guildId) {
3628
+ let manager = this.#scheduledEvents.get(guildId);
3629
+ if (!manager)
3630
+ this.#scheduledEvents.set(guildId, manager = new GuildScheduledEventManager(this.#rest, guildId));
3631
+ return manager;
3632
+ }
3077
3633
  async create(options) {
3078
3634
  const data = await this.#rest.post("/guilds", options);
3079
3635
  return this.upsert(new Guild(data));
@@ -3109,7 +3665,7 @@ class GuildManager extends ResourceManager {
3109
3665
  params.set("after", options.after);
3110
3666
  if (options.limit !== undefined)
3111
3667
  params.set("limit", String(Math.min(100, Math.max(1, options.limit))));
3112
- return this.#rest.get(withQuery(Routes.guildAuditLog(guildId), params));
3668
+ return this.#rest.get(withQuery2(Routes.guildAuditLog(guildId), params));
3113
3669
  }
3114
3670
  async fetchMembers(guildId, options = {}) {
3115
3671
  const params = new URLSearchParams;
@@ -3117,7 +3673,7 @@ class GuildManager extends ResourceManager {
3117
3673
  params.set("limit", String(Math.min(1000, Math.max(1, options.limit))));
3118
3674
  if (options.after)
3119
3675
  params.set("after", options.after);
3120
- return this.#rest.get(withQuery(Routes.guildMembers(guildId), params));
3676
+ return this.#rest.get(withQuery2(Routes.guildMembers(guildId), params));
3121
3677
  }
3122
3678
  async fetchAutoModerationRules(guildId) {
3123
3679
  const rules = await this.#rest.get(Routes.guildAutoModerationRules(guildId));
@@ -3248,9 +3804,7 @@ class GuildMemberManager extends ResourceManager {
3248
3804
  this.delete(userId);
3249
3805
  }
3250
3806
  async ban(userId, options = {}) {
3251
- await this.#rest.put(Routes.guildBan(this.guildId, userId), {
3252
- delete_message_seconds: options.deleteMessageSeconds
3253
- });
3807
+ await this.#rest.put(Routes.guildBan(this.guildId, userId), { delete_message_seconds: options.deleteMessageSeconds }, { reason: options.reason });
3254
3808
  this.delete(userId);
3255
3809
  }
3256
3810
  async unban(userId) {
@@ -3382,6 +3936,9 @@ class ChannelManager extends Manager {
3382
3936
  }
3383
3937
  return manager;
3384
3938
  }
3939
+ permissionOverwrites(channelId) {
3940
+ return new PermissionOverwriteManager(this.#rest, channelId);
3941
+ }
3385
3942
  threads(channelId) {
3386
3943
  return new ThreadManager(this.#rest, this.#context, channelId);
3387
3944
  }
@@ -3393,7 +3950,7 @@ class ChannelManager extends Manager {
3393
3950
  }
3394
3951
  upsert(data) {
3395
3952
  const existing = this.get(data.id);
3396
- const channel = new Channel(data, this.#context);
3953
+ const channel = createChannel(data, this.#context);
3397
3954
  if (existing) {
3398
3955
  Object.assign(existing, channel);
3399
3956
  return existing;
@@ -3501,35 +4058,593 @@ class ChannelManager extends Manager {
3501
4058
  async unpinMessage(channelId, messageId) {
3502
4059
  await this.#rest.delete(Routes.channelPin(channelId, messageId));
3503
4060
  }
3504
- createThreadFromMessage(channelId, messageId, options) {
3505
- return this.threads(channelId).createFromMessage(messageId, options);
4061
+ createThreadFromMessage(channelId, messageId, options) {
4062
+ return this.threads(channelId).createFromMessage(messageId, options);
4063
+ }
4064
+ async fetchPage(channelId, options = {}) {
4065
+ const limit = Math.min(100, Math.max(1, options.limit ?? 50));
4066
+ const messages = await this.fetchMessages(channelId, {
4067
+ ...options,
4068
+ limit
4069
+ });
4070
+ return {
4071
+ messages,
4072
+ before: messages[0]?.id,
4073
+ after: messages[messages.length - 1]?.id,
4074
+ hasMore: messages.length === limit
4075
+ };
4076
+ }
4077
+ async bulkDelete(channelId, messageIds) {
4078
+ return this.bulkDeleteMessages(channelId, messageIds);
4079
+ }
4080
+ clear() {
4081
+ this.#messageManagers.clear();
4082
+ super.clear();
4083
+ }
4084
+ }
4085
+ function isMessageQuery(value) {
4086
+ return typeof value === "object" && value !== null && !(Symbol.iterator in value);
4087
+ }
4088
+
4089
+ // packages/ws/src/session.ts
4090
+ class GatewaySession {
4091
+ #state = { status: "none" };
4092
+ #sequence = null;
4093
+ #generation = 0;
4094
+ #lastInvalidation;
4095
+ get lastInvalidation() {
4096
+ return this.#lastInvalidation;
4097
+ }
4098
+ get generation() {
4099
+ return this.#generation;
4100
+ }
4101
+ get sessionId() {
4102
+ return this.#state.status === "established" ? this.#state.sessionId : undefined;
4103
+ }
4104
+ get resumeURL() {
4105
+ return this.#state.status === "established" ? this.#state.resumeURL : undefined;
4106
+ }
4107
+ get sequence() {
4108
+ return this.#sequence;
4109
+ }
4110
+ get canResume() {
4111
+ return this.#state.status === "established" && this.#sequence !== null;
4112
+ }
4113
+ beginConnection() {
4114
+ return ++this.#generation;
4115
+ }
4116
+ owns(token) {
4117
+ return token === this.#generation;
4118
+ }
4119
+ recordSequence(sequence, token) {
4120
+ if (!this.owns(token))
4121
+ return false;
4122
+ if (!Number.isInteger(sequence))
4123
+ return false;
4124
+ this.#sequence = sequence;
4125
+ return true;
4126
+ }
4127
+ activate(payload, token) {
4128
+ if (!this.owns(token))
4129
+ return false;
4130
+ const ready = payload;
4131
+ if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string")
4132
+ return false;
4133
+ this.#state = {
4134
+ status: "established",
4135
+ sessionId: ready.session_id,
4136
+ resumeURL: ready.resume_gateway_url
4137
+ };
4138
+ return true;
4139
+ }
4140
+ invalidate(token, reason = "explicit") {
4141
+ if (!this.owns(token))
4142
+ return false;
4143
+ this.#lastInvalidation = reason;
4144
+ this.#state = { status: "none" };
4145
+ this.#sequence = null;
4146
+ return true;
4147
+ }
4148
+ resumeInfo() {
4149
+ if (this.#state.status !== "established")
4150
+ return;
4151
+ if (this.#sequence === null)
4152
+ return;
4153
+ const { sessionId, resumeURL } = this.#state;
4154
+ return { sessionId, sequence: this.#sequence, resumeURL };
4155
+ }
4156
+ handshake() {
4157
+ const resume = this.resumeInfo();
4158
+ return resume ? { type: "resume", ...resume } : { type: "identify" };
4159
+ }
4160
+ connectURL(fallback) {
4161
+ return this.resumeInfo()?.resumeURL ?? fallback;
4162
+ }
4163
+ }
4164
+
4165
+ // packages/ws/src/heartbeat.ts
4166
+ var MAX_MONITOR_INTERVAL = 1000;
4167
+
4168
+ class GatewayHeartbeat {
4169
+ #options;
4170
+ #now;
4171
+ #interval = 0;
4172
+ #acknowledged = true;
4173
+ #sentAt = 0;
4174
+ #latency = -1;
4175
+ #lastMessageAt = 0;
4176
+ #zombieReported = false;
4177
+ #timer;
4178
+ #initialTimer;
4179
+ #ackTimer;
4180
+ #monitorTimer;
4181
+ constructor(options) {
4182
+ if (options.zombieTimeout <= options.ackTimeout)
4183
+ throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
4184
+ this.#options = options;
4185
+ this.#now = options.now ?? (() => Date.now());
4186
+ }
4187
+ get latency() {
4188
+ return this.#latency;
4189
+ }
4190
+ get interval() {
4191
+ return this.#interval;
4192
+ }
4193
+ get acknowledged() {
4194
+ return this.#acknowledged;
4195
+ }
4196
+ get silentFor() {
4197
+ return this.#lastMessageAt === 0 ? 0 : this.#now() - this.#lastMessageAt;
4198
+ }
4199
+ get stalenessDeadline() {
4200
+ return Math.max(this.#options.zombieTimeout, this.#interval + this.#options.ackTimeout);
4201
+ }
4202
+ isHealthy() {
4203
+ if (!this.#acknowledged)
4204
+ return false;
4205
+ if (this.#lastMessageAt === 0)
4206
+ return true;
4207
+ return this.silentFor < this.stalenessDeadline;
4208
+ }
4209
+ watch() {
4210
+ this.#lastMessageAt = this.#now();
4211
+ this.#zombieReported = false;
4212
+ this.#startMonitor();
4213
+ }
4214
+ receivedMessage() {
4215
+ this.#lastMessageAt = this.#now();
4216
+ this.#zombieReported = false;
4217
+ }
4218
+ start(interval) {
4219
+ this.#clearBeatTimers();
4220
+ this.#interval = interval;
4221
+ this.#acknowledged = true;
4222
+ this.#initialTimer = setTimeout(() => this.sendHeartbeat(), Math.random() * interval);
4223
+ this.#timer = setInterval(() => this.sendHeartbeat(), interval);
4224
+ }
4225
+ stop() {
4226
+ this.#clearBeatTimers();
4227
+ if (this.#monitorTimer)
4228
+ clearInterval(this.#monitorTimer);
4229
+ this.#monitorTimer = undefined;
4230
+ }
4231
+ sendHeartbeat() {
4232
+ this.#acknowledged = false;
4233
+ this.#sentAt = this.#now();
4234
+ if (!this.#options.send(this.#options.sequence())) {
4235
+ this.#options.onError(new Error("Unable to send Gateway heartbeat because the WebSocket is not open."));
4236
+ return;
4237
+ }
4238
+ this.#clearAckTimer();
4239
+ this.#ackTimer = setTimeout(() => {
4240
+ if (this.#acknowledged)
4241
+ return;
4242
+ this.#options.onTimeout({
4243
+ type: "ack",
4244
+ elapsedMs: this.#now() - this.#sentAt
4245
+ });
4246
+ }, this.#options.ackTimeout);
4247
+ }
4248
+ acknowledge() {
4249
+ this.#acknowledged = true;
4250
+ this.#latency = this.#now() - this.#sentAt;
4251
+ this.#clearAckTimer();
4252
+ }
4253
+ #startMonitor() {
4254
+ if (this.#monitorTimer)
4255
+ clearInterval(this.#monitorTimer);
4256
+ const interval = Math.max(1, Math.min(MAX_MONITOR_INTERVAL, this.#options.ackTimeout, this.#options.zombieTimeout / 2));
4257
+ this.#monitorTimer = setInterval(() => this.#checkStaleness(), interval);
4258
+ }
4259
+ #checkStaleness() {
4260
+ if (!this.#options.isConnected() || this.#lastMessageAt === 0)
4261
+ return;
4262
+ const silentFor = this.silentFor;
4263
+ const deadline = this.stalenessDeadline;
4264
+ if (silentFor < deadline || this.#zombieReported)
4265
+ return;
4266
+ this.#zombieReported = true;
4267
+ this.#options.onTimeout({ type: "zombie", silentFor, deadline });
4268
+ }
4269
+ #clearAckTimer() {
4270
+ if (this.#ackTimer)
4271
+ clearTimeout(this.#ackTimer);
4272
+ this.#ackTimer = undefined;
4273
+ }
4274
+ #clearBeatTimers() {
4275
+ if (this.#timer)
4276
+ clearInterval(this.#timer);
4277
+ if (this.#initialTimer)
4278
+ clearTimeout(this.#initialTimer);
4279
+ this.#clearAckTimer();
4280
+ this.#timer = undefined;
4281
+ this.#initialTimer = undefined;
4282
+ }
4283
+ }
4284
+
4285
+ // packages/ws/src/close-codes.ts
4286
+ var GatewayCloseCodes = {
4287
+ UnknownError: 4000,
4288
+ UnknownOpcode: 4001,
4289
+ DecodeError: 4002,
4290
+ NotAuthenticated: 4003,
4291
+ AuthenticationFailed: 4004,
4292
+ AlreadyAuthenticated: 4005,
4293
+ InvalidSeq: 4007,
4294
+ RateLimited: 4008,
4295
+ SessionTimedOut: 4009,
4296
+ InvalidShard: 4010,
4297
+ ShardingRequired: 4011,
4298
+ InvalidAPIVersion: 4012,
4299
+ InvalidIntents: 4013,
4300
+ DisallowedIntents: 4014
4301
+ };
4302
+
4303
+ // packages/ws/src/reconnect.ts
4304
+ var FATAL_CLOSE_CODES = [
4305
+ GatewayCloseCodes.AuthenticationFailed,
4306
+ GatewayCloseCodes.InvalidShard,
4307
+ GatewayCloseCodes.ShardingRequired,
4308
+ GatewayCloseCodes.InvalidAPIVersion,
4309
+ GatewayCloseCodes.InvalidIntents,
4310
+ GatewayCloseCodes.DisallowedIntents
4311
+ ];
4312
+ var IDENTIFY_CLOSE_CODES = [
4313
+ GatewayCloseCodes.InvalidSeq,
4314
+ GatewayCloseCodes.SessionTimedOut
4315
+ ];
4316
+ function classifyCloseCode(code, canResume) {
4317
+ if (FATAL_CLOSE_CODES.includes(code))
4318
+ return "stop";
4319
+ if (IDENTIFY_CLOSE_CODES.includes(code))
4320
+ return "identify";
4321
+ return canResume ? "resume" : "identify";
4322
+ }
4323
+
4324
+ class GatewayReconnect {
4325
+ #options;
4326
+ #random;
4327
+ #attempt = 0;
4328
+ #timer;
4329
+ #promise;
4330
+ #resolve;
4331
+ #reject;
4332
+ constructor(options) {
4333
+ this.#options = options;
4334
+ this.#random = options.random ?? Math.random;
4335
+ }
4336
+ get enabled() {
4337
+ return this.#options.enabled;
4338
+ }
4339
+ get attempts() {
4340
+ return this.#attempt;
4341
+ }
4342
+ get pending() {
4343
+ return this.#timer !== undefined;
4344
+ }
4345
+ get exhausted() {
4346
+ return this.#attempt >= this.#options.maxAttempts;
4347
+ }
4348
+ get connecting() {
4349
+ return this.#promise !== undefined;
4350
+ }
4351
+ get attemptPromise() {
4352
+ return this.#promise;
4353
+ }
4354
+ classifyClose(code, canResume) {
4355
+ return classifyCloseCode(code, canResume);
4356
+ }
4357
+ canReconnect() {
4358
+ return this.#options.enabled && !this.exhausted;
4359
+ }
4360
+ schedule(run) {
4361
+ if (!this.#options.enabled)
4362
+ return { scheduled: false, reason: "disabled" };
4363
+ if (this.#timer)
4364
+ return { scheduled: false, reason: "pending" };
4365
+ if (this.exhausted)
4366
+ return { scheduled: false, reason: "exhausted" };
4367
+ const attempt = this.#attempt;
4368
+ const delay = Math.min(this.#options.maxDelay, this.#options.baseDelay * 2 ** this.#attempt++);
4369
+ const jitter = this.#random() * Math.max(1, delay * 0.25);
4370
+ const delayMs = delay + jitter;
4371
+ this.#timer = setTimeout(() => {
4372
+ this.#timer = undefined;
4373
+ run();
4374
+ }, delayMs);
4375
+ return { scheduled: true, delayMs, attempt };
4376
+ }
4377
+ cancel() {
4378
+ if (this.#timer)
4379
+ clearTimeout(this.#timer);
4380
+ this.#timer = undefined;
4381
+ }
4382
+ reset() {
4383
+ this.#attempt = 0;
4384
+ }
4385
+ attempt(start) {
4386
+ if (this.#promise)
4387
+ return this.#promise;
4388
+ this.#promise = new Promise((resolve, reject) => {
4389
+ this.#resolve = resolve;
4390
+ this.#reject = reject;
4391
+ start();
4392
+ });
4393
+ return this.#promise;
4394
+ }
4395
+ settle(error) {
4396
+ const resolve = this.#resolve;
4397
+ const reject = this.#reject;
4398
+ this.#resolve = undefined;
4399
+ this.#reject = undefined;
4400
+ this.#promise = undefined;
4401
+ if (error)
4402
+ reject?.(error);
4403
+ else
4404
+ resolve?.();
4405
+ }
4406
+ }
4407
+
4408
+ // packages/ws/src/decoder.ts
4409
+ import {
4410
+ createInflate,
4411
+ constants as zlibConstants
4412
+ } from "zlib";
4413
+ async function toBytes(data) {
4414
+ if (data instanceof Uint8Array)
4415
+ return data;
4416
+ if (data instanceof ArrayBuffer)
4417
+ return new Uint8Array(data);
4418
+ if (typeof Blob !== "undefined" && data instanceof Blob)
4419
+ return new Uint8Array(await data.arrayBuffer());
4420
+ throw new TypeError("Gateway returned an unsupported compressed frame type.");
4421
+ }
4422
+
4423
+ class PlainTextDecoder {
4424
+ push(data) {
4425
+ return [String(data)];
4426
+ }
4427
+ reset() {}
4428
+ }
4429
+
4430
+ class ZlibStreamDecoder {
4431
+ #create;
4432
+ #inflate;
4433
+ #chunks = [];
4434
+ #failure;
4435
+ #pendingReject;
4436
+ constructor(create = () => createInflate()) {
4437
+ this.#create = create;
4438
+ }
4439
+ push(data) {
4440
+ if (typeof data === "string")
4441
+ return [data];
4442
+ return this.#inflateFrame(data);
4443
+ }
4444
+ async#inflateFrame(data) {
4445
+ const chunk = await toBytes(data);
4446
+ const inflate = this.#inflate ??= this.#open();
4447
+ if (this.#failure)
4448
+ throw this.#failure;
4449
+ const complete = chunk.length >= 4 && chunk[chunk.length - 4] === 0 && chunk[chunk.length - 3] === 0 && chunk[chunk.length - 2] === 255 && chunk[chunk.length - 1] === 255;
4450
+ await this.#awaiting((settle) => inflate.write(chunk, (error) => error ? settle(error) : settle()));
4451
+ if (!complete)
4452
+ return [];
4453
+ await this.#awaiting((settle) => inflate.flush(zlibConstants.Z_SYNC_FLUSH, () => settle()));
4454
+ const parts = this.#chunks;
4455
+ this.#chunks = [];
4456
+ if (parts.length === 0)
4457
+ return [];
4458
+ const total = parts.reduce((size, part) => size + part.length, 0);
4459
+ const merged = new Uint8Array(total);
4460
+ let offset = 0;
4461
+ for (const part of parts) {
4462
+ merged.set(part, offset);
4463
+ offset += part.length;
4464
+ }
4465
+ return [new TextDecoder().decode(merged)];
4466
+ }
4467
+ reset() {
4468
+ const inflate = this.#inflate;
4469
+ this.#inflate = undefined;
4470
+ this.#chunks = [];
4471
+ this.#failure = undefined;
4472
+ this.#pendingReject = undefined;
4473
+ if (!inflate)
4474
+ return;
4475
+ inflate.removeAllListeners();
4476
+ try {
4477
+ inflate.close();
4478
+ } catch {}
4479
+ }
4480
+ #awaiting(run) {
4481
+ if (this.#failure)
4482
+ return Promise.reject(this.#failure);
4483
+ return new Promise((resolve, reject) => {
4484
+ this.#pendingReject = reject;
4485
+ run((error) => {
4486
+ this.#pendingReject = undefined;
4487
+ if (error)
4488
+ reject(error);
4489
+ else if (this.#failure)
4490
+ reject(this.#failure);
4491
+ else
4492
+ resolve();
4493
+ });
4494
+ });
4495
+ }
4496
+ #open() {
4497
+ const inflate = this.#create();
4498
+ inflate.on("data", (chunk) => this.#chunks.push(chunk));
4499
+ inflate.on("error", (error) => {
4500
+ this.#failure = error;
4501
+ const reject = this.#pendingReject;
4502
+ this.#pendingReject = undefined;
4503
+ reject?.(error);
4504
+ });
4505
+ return inflate;
4506
+ }
4507
+ }
4508
+ function createDecoder(compress) {
4509
+ return compress ? new ZlibStreamDecoder : new PlainTextDecoder;
4510
+ }
4511
+
4512
+ // packages/ws/src/transport.ts
4513
+ var OPEN = 1;
4514
+
4515
+ class WebSocketTransport {
4516
+ #handlers;
4517
+ #decoder;
4518
+ #createSocket;
4519
+ #compress;
4520
+ #socket;
4521
+ #generation = 0;
4522
+ #queue = Promise.resolve();
4523
+ constructor(options) {
4524
+ this.#handlers = options.handlers;
4525
+ this.#compress = options.compress ?? false;
4526
+ this.#decoder = options.decoder ?? createDecoder(options.compress);
4527
+ this.#createSocket = options.createSocket ?? ((url) => new WebSocket(url));
4528
+ }
4529
+ get connected() {
4530
+ return this.#socket?.readyState === OPEN;
4531
+ }
4532
+ get live() {
4533
+ return this.#socket !== undefined && this.#socket.readyState <= OPEN;
4534
+ }
4535
+ get generation() {
4536
+ return this.#generation;
4537
+ }
4538
+ connect(url) {
4539
+ this.#discard(1000, "Superseded by a new connection");
4540
+ const generation = ++this.#generation;
4541
+ this.#decoder.reset();
4542
+ this.#queue = Promise.resolve();
4543
+ let socket;
4544
+ try {
4545
+ socket = this.#createSocket(url);
4546
+ } catch (error) {
4547
+ return { ok: false, error: this.#asError(error) };
4548
+ }
4549
+ if (this.#compress) {
4550
+ try {
4551
+ socket.binaryType = "arraybuffer";
4552
+ } catch {}
4553
+ }
4554
+ this.#socket = socket;
4555
+ socket.addEventListener("open", () => {
4556
+ if (!this.#owns(generation))
4557
+ return;
4558
+ this.#handlers.onOpen();
4559
+ });
4560
+ socket.addEventListener("message", (event) => {
4561
+ if (!this.#owns(generation))
4562
+ return;
4563
+ let decoded;
4564
+ try {
4565
+ decoded = this.#decoder.push(event.data);
4566
+ } catch (error) {
4567
+ this.#handlers.onError(this.#asError(error));
4568
+ return;
4569
+ }
4570
+ if (!(decoded instanceof Promise)) {
4571
+ for (const frame of decoded)
4572
+ this.#handlers.onFrame(frame);
4573
+ return;
4574
+ }
4575
+ this.#queue = this.#queue.then(async () => {
4576
+ const frames = await decoded;
4577
+ if (!this.#owns(generation))
4578
+ return;
4579
+ for (const frame of frames)
4580
+ this.#handlers.onFrame(frame);
4581
+ }).catch((error) => {
4582
+ if (!this.#owns(generation))
4583
+ return;
4584
+ this.#handlers.onError(this.#asError(error));
4585
+ });
4586
+ });
4587
+ socket.addEventListener("close", (event) => {
4588
+ if (!this.#owns(generation))
4589
+ return;
4590
+ this.#socket = undefined;
4591
+ this.#generation++;
4592
+ this.#decoder.reset();
4593
+ this.#handlers.onClose(event.code, event.reason ?? "");
4594
+ });
4595
+ socket.addEventListener("error", () => {
4596
+ if (!this.#owns(generation))
4597
+ return;
4598
+ this.#handlers.onError(new Error("Gateway WebSocket error"));
4599
+ });
4600
+ return { ok: true };
4601
+ }
4602
+ send(data) {
4603
+ if (!this.connected)
4604
+ return false;
4605
+ try {
4606
+ this.#socket.send(data);
4607
+ return true;
4608
+ } catch (error) {
4609
+ this.#handlers.onError(this.#asError(error));
4610
+ return false;
4611
+ }
4612
+ }
4613
+ close(code = 1000, reason = "") {
4614
+ const socket = this.#socket;
4615
+ if (!socket)
4616
+ return;
4617
+ try {
4618
+ socket.close(code, reason);
4619
+ } catch (error) {
4620
+ this.#handlers.onError(this.#asError(error));
4621
+ }
4622
+ }
4623
+ destroy(code = 1000, reason = "") {
4624
+ this.#discard(code, reason);
4625
+ this.#decoder.reset();
3506
4626
  }
3507
- async fetchPage(channelId, options = {}) {
3508
- const limit = Math.min(100, Math.max(1, options.limit ?? 50));
3509
- const messages = await this.fetchMessages(channelId, {
3510
- ...options,
3511
- limit
3512
- });
3513
- return {
3514
- messages,
3515
- before: messages[0]?.id,
3516
- after: messages[messages.length - 1]?.id,
3517
- hasMore: messages.length === limit
3518
- };
4627
+ #owns(generation) {
4628
+ return generation === this.#generation;
3519
4629
  }
3520
- async bulkDelete(channelId, messageIds) {
3521
- return this.bulkDeleteMessages(channelId, messageIds);
4630
+ #discard(code, reason) {
4631
+ const socket = this.#socket;
4632
+ if (!socket)
4633
+ return;
4634
+ this.#generation++;
4635
+ this.#socket = undefined;
4636
+ try {
4637
+ socket.close(code, reason);
4638
+ } catch (error) {
4639
+ this.#handlers.onError(this.#asError(error));
4640
+ }
3522
4641
  }
3523
- clear() {
3524
- this.#messageManagers.clear();
3525
- super.clear();
4642
+ #asError(error) {
4643
+ return error instanceof Error ? error : new Error(String(error));
3526
4644
  }
3527
4645
  }
3528
- function isMessageQuery(value) {
3529
- return typeof value === "object" && value !== null && !(Symbol.iterator in value);
3530
- }
3531
4646
 
3532
- // packages/ws/src/index.ts
4647
+ // packages/ws/src/opcodes.ts
3533
4648
  var GatewayOpcodes = {
3534
4649
  Dispatch: 0,
3535
4650
  Heartbeat: 1,
@@ -3543,33 +4658,142 @@ var GatewayOpcodes = {
3543
4658
  Hello: 10,
3544
4659
  HeartbeatAck: 11
3545
4660
  };
3546
- var GatewayCloseCodes = {
3547
- UnknownError: 4000,
3548
- UnknownOpcode: 4001,
3549
- DecodeError: 4002,
3550
- NotAuthenticated: 4003,
3551
- AuthenticationFailed: 4004,
3552
- AlreadyAuthenticated: 4005,
3553
- InvalidSeq: 4007,
3554
- RateLimited: 4008,
3555
- SessionTimedOut: 4009,
3556
- InvalidShard: 4010,
3557
- ShardingRequired: 4011,
3558
- InvalidAPIVersion: 4012,
3559
- InvalidIntents: 4013,
3560
- DisallowedIntents: 4014
3561
- };
4661
+
4662
+ // packages/ws/src/protocol.ts
4663
+ function classifyFrame(raw) {
4664
+ let payload;
4665
+ try {
4666
+ payload = JSON.parse(raw);
4667
+ } catch (error) {
4668
+ return {
4669
+ sequence: null,
4670
+ action: {
4671
+ type: "invalid",
4672
+ violation: "invalid-json",
4673
+ message: "Gateway returned invalid JSON",
4674
+ cause: error
4675
+ }
4676
+ };
4677
+ }
4678
+ return classifyPayload(payload);
4679
+ }
4680
+ function classifyPayload(payload) {
4681
+ if (payload === null || typeof payload !== "object" || typeof payload.op !== "number")
4682
+ return {
4683
+ sequence: null,
4684
+ action: {
4685
+ type: "invalid",
4686
+ violation: "invalid-payload",
4687
+ message: "Gateway returned an invalid payload"
4688
+ }
4689
+ };
4690
+ const frame = payload;
4691
+ const sequence = typeof frame.s === "number" ? frame.s : null;
4692
+ switch (frame.op) {
4693
+ case GatewayOpcodes.Dispatch:
4694
+ return {
4695
+ sequence,
4696
+ action: {
4697
+ type: "dispatch",
4698
+ event: typeof frame.t === "string" ? frame.t : null,
4699
+ data: frame.d
4700
+ }
4701
+ };
4702
+ case GatewayOpcodes.Hello: {
4703
+ const interval = frame.d?.heartbeat_interval;
4704
+ if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0)
4705
+ return {
4706
+ sequence,
4707
+ action: {
4708
+ type: "invalid",
4709
+ violation: "invalid-hello",
4710
+ message: "Gateway HELLO payload contains an invalid heartbeat interval"
4711
+ }
4712
+ };
4713
+ return {
4714
+ sequence,
4715
+ action: { type: "hello", heartbeatInterval: interval }
4716
+ };
4717
+ }
4718
+ case GatewayOpcodes.Heartbeat:
4719
+ return { sequence, action: { type: "heartbeat" } };
4720
+ case GatewayOpcodes.HeartbeatAck:
4721
+ return {
4722
+ sequence,
4723
+ action: { type: "heartbeat-ack", data: frame.d }
4724
+ };
4725
+ case GatewayOpcodes.Reconnect:
4726
+ return { sequence, action: { type: "reconnect" } };
4727
+ case GatewayOpcodes.InvalidSession:
4728
+ return {
4729
+ sequence,
4730
+ action: {
4731
+ type: "invalid-session",
4732
+ resumable: frame.d === true
4733
+ }
4734
+ };
4735
+ default:
4736
+ return { sequence, action: { type: "unknown", opcode: frame.op } };
4737
+ }
4738
+ }
4739
+ function identifyPayload(options) {
4740
+ const props = options.properties ?? {};
4741
+ const os = props.os ?? "Android";
4742
+ const browser = props.browser ?? "Discord Android";
4743
+ const device = props.device ?? "Discord Android";
4744
+ return {
4745
+ op: GatewayOpcodes.Identify,
4746
+ d: {
4747
+ token: options.token,
4748
+ intents: options.intents,
4749
+ properties: {
4750
+ os,
4751
+ browser,
4752
+ device,
4753
+ $os: os,
4754
+ $browser: browser,
4755
+ $device: device,
4756
+ ...props
4757
+ },
4758
+ presence: {
4759
+ since: options.presence?.since ?? null,
4760
+ activities: options.presence?.activities ?? [],
4761
+ status: options.presence?.status ?? "online",
4762
+ afk: Boolean(options.presence?.afk)
4763
+ },
4764
+ shard: [options.shardId, options.shardCount]
4765
+ },
4766
+ s: null,
4767
+ t: null
4768
+ };
4769
+ }
4770
+ function resumePayload(token, resume) {
4771
+ return {
4772
+ op: GatewayOpcodes.Resume,
4773
+ d: {
4774
+ token,
4775
+ session_id: resume.sessionId,
4776
+ seq: resume.sequence
4777
+ },
4778
+ s: null,
4779
+ t: null
4780
+ };
4781
+ }
4782
+ function heartbeatPayload(sequence) {
4783
+ return { op: GatewayOpcodes.Heartbeat, d: sequence, s: null, t: null };
4784
+ }
4785
+ // packages/ws/src/state.ts
3562
4786
  var GatewayState;
3563
- ((GatewayState2) => {
3564
- GatewayState2["Connect"] = "CONNECT";
3565
- GatewayState2["Hello"] = "HELLO";
3566
- GatewayState2["Identify"] = "IDENTIFY";
3567
- GatewayState2["Resume"] = "RESUME";
3568
- GatewayState2["Ready"] = "READY";
3569
- GatewayState2["Dispatch"] = "DISPATCH";
3570
- GatewayState2["Heartbeat"] = "HEARTBEAT";
3571
- GatewayState2["Reconnect"] = "RECONNECT";
3572
- GatewayState2["Closed"] = "CLOSED";
4787
+ ((GatewayState) => {
4788
+ GatewayState["Connect"] = "CONNECT";
4789
+ GatewayState["Hello"] = "HELLO";
4790
+ GatewayState["Identify"] = "IDENTIFY";
4791
+ GatewayState["Resume"] = "RESUME";
4792
+ GatewayState["Ready"] = "READY";
4793
+ GatewayState["Dispatch"] = "DISPATCH";
4794
+ GatewayState["Heartbeat"] = "HEARTBEAT";
4795
+ GatewayState["Reconnect"] = "RECONNECT";
4796
+ GatewayState["Closed"] = "CLOSED";
3573
4797
  })(GatewayState ||= {});
3574
4798
  class GatewayError extends Error {
3575
4799
  code;
@@ -3579,33 +4803,43 @@ class GatewayError extends Error {
3579
4803
  this.code = code;
3580
4804
  }
3581
4805
  }
4806
+ // packages/ws/src/send-budget.ts
4807
+ class SendBudget {
4808
+ #timestamps = [];
4809
+ #limit;
4810
+ #windowMs;
4811
+ constructor(limit = 115, windowMs = 60000) {
4812
+ this.#limit = limit;
4813
+ this.#windowMs = windowMs;
4814
+ }
4815
+ get used() {
4816
+ return this.#timestamps.length;
4817
+ }
4818
+ allows(privileged, now = Date.now()) {
4819
+ while (this.#timestamps.length && now - this.#timestamps[0] >= this.#windowMs)
4820
+ this.#timestamps.shift();
4821
+ return privileged || this.#timestamps.length < this.#limit;
4822
+ }
4823
+ record(now = Date.now()) {
4824
+ this.#timestamps.push(now);
4825
+ }
4826
+ }
4827
+
4828
+ // packages/ws/src/index.ts
4829
+ var DEFAULT_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
3582
4830
 
3583
4831
  class Gateway {
3584
4832
  state = "CONNECT" /* Connect */;
3585
4833
  #options;
3586
- #ws;
3587
- #sequence = null;
3588
- #sessionId;
3589
- #resumeURL;
3590
- #heartbeatTimer;
3591
- #initialHeartbeat;
3592
- #heartbeatAckTimer;
3593
- #heartbeatACK = true;
3594
- #heartbeatInterval = 0;
3595
- #heartbeatSentAt = 0;
3596
- #lastMessageAt = 0;
3597
- #zombieTimer;
3598
- #zombieReported = false;
4834
+ #transport;
4835
+ #session = new GatewaySession;
4836
+ #token = 0;
4837
+ #heartbeat;
3599
4838
  #closed = false;
3600
- #attempt = 0;
3601
- #reconnectTimer;
3602
- #connectPromise;
3603
- #resolveConnect;
3604
- #rejectConnect;
4839
+ #reconnect;
3605
4840
  #listeners = new Map;
3606
- #sendTimestamps = [];
4841
+ #sendBudget = new SendBudget;
3607
4842
  ping = -1;
3608
- #decompressor;
3609
4843
  constructor(options) {
3610
4844
  if (!options.token?.trim())
3611
4845
  throw new TypeError("A Gateway token is required.");
@@ -3632,41 +4866,72 @@ class Gateway {
3632
4866
  afk: false,
3633
4867
  since: null
3634
4868
  },
3635
- ...options
4869
+ ...options,
4870
+ intents: resolvedIntents
3636
4871
  };
3637
4872
  if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
3638
4873
  throw new RangeError("Gateway shardId must be within the configured shard count.");
3639
4874
  if (this.#options.zombieTimeout <= this.#options.heartbeatAckTimeout)
3640
4875
  throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
4876
+ this.#transport = new WebSocketTransport({
4877
+ compress: this.#options.compress,
4878
+ createSocket: options.createSocket,
4879
+ handlers: {
4880
+ onOpen: () => this.#onOpen(),
4881
+ onFrame: (raw) => this.#message(raw, this.#token),
4882
+ onClose: (code) => this.#close(code, this.#token),
4883
+ onError: (error) => this.#emitError(error instanceof GatewayError ? error : new GatewayError(error.message, undefined, {
4884
+ cause: error
4885
+ }))
4886
+ }
4887
+ });
4888
+ this.#reconnect = new GatewayReconnect({
4889
+ enabled: this.#options.reconnect,
4890
+ maxAttempts: this.#options.maxReconnectAttempts,
4891
+ baseDelay: this.#options.reconnectBaseDelay,
4892
+ maxDelay: this.#options.reconnectMaxDelay
4893
+ });
4894
+ this.#heartbeat = new GatewayHeartbeat({
4895
+ ackTimeout: this.#options.heartbeatAckTimeout,
4896
+ zombieTimeout: this.#options.zombieTimeout,
4897
+ send: (sequence) => this.#dispatch(heartbeatPayload(sequence), true),
4898
+ sequence: () => this.#session.sequence,
4899
+ isConnected: () => !this.#closed && this.#transport.connected,
4900
+ onTimeout: (timeout) => this.#onHeartbeatTimeout(timeout),
4901
+ onError: (error) => this.#emitError(error)
4902
+ });
4903
+ }
4904
+ #onHeartbeatTimeout(timeout) {
4905
+ if (timeout.type === "zombie") {
4906
+ this.#emit("zombie", {
4907
+ silentFor: timeout.silentFor,
4908
+ timeout: timeout.deadline
4909
+ });
4910
+ this.#emitError(new GatewayError(`Gateway connection appears stale after ${timeout.silentFor}ms without traffic.`));
4911
+ } else {
4912
+ this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${timeout.elapsedMs}ms.`));
4913
+ }
4914
+ this.#transport.close(1001, timeout.type === "zombie" ? "Zombie Gateway connection" : "Heartbeat timeout");
3641
4915
  }
3642
- connect(url = "wss://gateway.discord.gg/?v=10&encoding=json") {
4916
+ connect(url = DEFAULT_GATEWAY_URL) {
3643
4917
  if (this.state === "CLOSED" /* Closed */)
3644
4918
  throw new GatewayError("Gateway has been permanently closed.");
3645
- if (this.#connectPromise)
3646
- return this.#connectPromise;
4919
+ const inFlight = this.#reconnect.attemptPromise;
4920
+ if (inFlight)
4921
+ return inFlight;
4922
+ if (this.#transport.live)
4923
+ return Promise.resolve();
4924
+ this.#reconnect.cancel();
3647
4925
  this.#closed = false;
3648
4926
  this.#setState("CONNECT" /* Connect */);
3649
4927
  const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
3650
- if (this.#options.compress)
3651
- this.#initDecompressor();
3652
- this.#connectPromise = new Promise((resolve, reject) => {
3653
- this.#resolveConnect = resolve;
3654
- this.#rejectConnect = reject;
3655
- this.#open(connectURL);
3656
- });
3657
- return this.#connectPromise;
4928
+ return this.#reconnect.attempt(() => this.#open(connectURL));
3658
4929
  }
3659
4930
  close() {
3660
4931
  this.#closed = true;
3661
4932
  this.#clearTimers();
3662
4933
  this.#settleConnect(new GatewayError("Gateway connection closed before socket open."));
3663
- const ws = this.#ws;
3664
- this.#ws = undefined;
3665
- try {
3666
- ws?.close(1000, "Client closed connection");
3667
- } catch (error) {
3668
- this.#emitError(error);
3669
- }
4934
+ this.#transport.destroy(1000, "Client closed connection");
3670
4935
  this.#setState("CLOSED" /* Closed */);
3671
4936
  }
3672
4937
  emit(event, data) {
@@ -3691,23 +4956,17 @@ class Gateway {
3691
4956
  return this.#dispatch(payload, false);
3692
4957
  }
3693
4958
  #dispatch(payload, privileged) {
3694
- if (this.#ws?.readyState !== WebSocket.OPEN)
4959
+ if (!this.#transport.connected)
3695
4960
  return false;
3696
4961
  const now = Date.now();
3697
- while (this.#sendTimestamps.length && now - this.#sendTimestamps[0] >= 60000)
3698
- this.#sendTimestamps.shift();
3699
- if (!privileged && this.#sendTimestamps.length >= 115) {
4962
+ if (!this.#sendBudget.allows(privileged, now)) {
3700
4963
  this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
3701
4964
  return false;
3702
4965
  }
3703
- try {
3704
- this.#ws.send(JSON.stringify(payload));
3705
- this.#sendTimestamps.push(now);
3706
- return true;
3707
- } catch (error) {
3708
- this.#emitError(error);
4966
+ if (!this.#transport.send(JSON.stringify(payload)))
3709
4967
  return false;
3710
- }
4968
+ this.#sendBudget.record(now);
4969
+ return true;
3711
4970
  }
3712
4971
  setPresence(data) {
3713
4972
  this.#options.presence = { ...this.#options.presence, ...data };
@@ -3743,127 +5002,80 @@ class Gateway {
3743
5002
  #open(url) {
3744
5003
  if (this.#closed)
3745
5004
  return;
3746
- let ws;
3747
- try {
3748
- ws = new WebSocket(url);
3749
- } catch (error) {
3750
- const failure = this.#normalizeError(error);
3751
- this.#emitError(failure);
3752
- if (this.#options.reconnect) {
3753
- this.#setState("RECONNECT" /* Reconnect */);
3754
- this.#scheduleReconnect();
3755
- } else
3756
- this.#settleConnect(failure);
5005
+ this.#token = this.#session.beginConnection();
5006
+ const result = this.#transport.connect(url);
5007
+ if (result.ok)
3757
5008
  return;
5009
+ const failure = this.#normalizeError(result.error);
5010
+ this.#emitError(failure);
5011
+ if (this.#options.reconnect) {
5012
+ this.#setState("RECONNECT" /* Reconnect */);
5013
+ this.#scheduleReconnect();
5014
+ } else {
5015
+ this.#settleConnect(failure);
3758
5016
  }
3759
- this.#ws = ws;
3760
- ws.addEventListener("open", () => {
3761
- this.#lastMessageAt = Date.now();
3762
- this.#zombieReported = false;
3763
- this.#startZombieDetection();
3764
- this.#emit("open", undefined);
3765
- this.#settleConnect();
3766
- });
3767
- ws.addEventListener("message", (event) => {
3768
- this.#lastMessageAt = Date.now();
3769
- this.#zombieReported = false;
3770
- if (this.#options.compress && event.data instanceof ArrayBuffer) {
3771
- this.#decompress(new Uint8Array(event.data)).then((text) => this.#message(ws, text)).catch((err) => this.#emitError(err));
3772
- } else {
3773
- this.#message(ws, String(event.data));
3774
- }
3775
- });
3776
- ws.addEventListener("close", (event) => this.#close(ws, event.code));
3777
- ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
3778
- }
3779
- #startZombieDetection() {
3780
- if (this.#zombieTimer)
3781
- clearInterval(this.#zombieTimer);
3782
- const interval = Math.max(1, Math.min(1000, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
3783
- this.#zombieTimer = setInterval(() => {
3784
- if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
3785
- return;
3786
- const silentFor = Date.now() - this.#lastMessageAt;
3787
- const deadline = Math.max(this.#options.zombieTimeout, this.#heartbeatInterval + this.#options.heartbeatAckTimeout);
3788
- if (silentFor < deadline || this.#zombieReported)
3789
- return;
3790
- this.#zombieReported = true;
3791
- const error = new GatewayError(`Gateway connection appears stale after ${silentFor}ms without traffic.`);
3792
- this.#emit("zombie", { silentFor, timeout: deadline });
3793
- this.#emitError(error);
3794
- try {
3795
- this.#ws.close(1001, "Zombie Gateway connection");
3796
- } catch (closeError) {
3797
- this.#emitError(closeError);
3798
- }
3799
- }, interval);
3800
5017
  }
3801
- #message(ws, raw) {
3802
- let payload;
3803
- try {
3804
- payload = JSON.parse(raw);
3805
- } catch (error) {
3806
- this.#emitError(new GatewayError("Gateway returned invalid JSON", undefined, {
3807
- cause: error
3808
- }));
3809
- ws.close(1002, "Invalid JSON");
3810
- return;
3811
- }
3812
- if (!payload || typeof payload.op !== "number") {
3813
- this.#emitError(new GatewayError("Gateway returned an invalid payload"));
3814
- ws.close(1002, "Invalid payload");
5018
+ #onOpen() {
5019
+ this.#heartbeat.watch();
5020
+ this.#emit("open", undefined);
5021
+ this.#settleConnect();
5022
+ }
5023
+ #message(raw, token) {
5024
+ if (!this.#session.owns(token))
3815
5025
  return;
3816
- }
3817
- if (typeof payload.s === "number")
3818
- this.#sequence = payload.s;
3819
- switch (payload.op) {
3820
- case GatewayOpcodes.Dispatch:
5026
+ const { sequence, action } = classifyFrame(raw);
5027
+ if (sequence !== null)
5028
+ this.#session.recordSequence(sequence, token);
5029
+ this.#apply(action, token);
5030
+ }
5031
+ #apply(action, token) {
5032
+ switch (action.type) {
5033
+ case "dispatch":
3821
5034
  this.#setState("DISPATCH" /* Dispatch */);
3822
- this.#handleDispatch(payload.t, payload.d);
3823
- break;
3824
- case GatewayOpcodes.Hello:
3825
- this.#handleHello(payload.d);
3826
- break;
3827
- case GatewayOpcodes.Heartbeat:
5035
+ this.#handleDispatch(action.event, action.data, token);
5036
+ return;
5037
+ case "hello":
5038
+ this.#handleHello(action.heartbeatInterval);
5039
+ return;
5040
+ case "heartbeat":
3828
5041
  this.#setState("HEARTBEAT" /* Heartbeat */);
3829
- this.#sendHeartbeat();
3830
- break;
3831
- case GatewayOpcodes.HeartbeatAck:
3832
- this.#heartbeatACK = true;
3833
- this.ping = Date.now() - this.#heartbeatSentAt;
3834
- this.#clearHeartbeatAckTimer();
3835
- this.#emit("heartbeatAck", payload.d);
3836
- break;
3837
- case GatewayOpcodes.Reconnect:
3838
- ws.close(1001, "Server requested reconnect");
3839
- break;
3840
- case GatewayOpcodes.InvalidSession: {
3841
- const resumable = payload.d === true;
3842
- if (!resumable) {
3843
- this.#sessionId = undefined;
3844
- this.#sequence = null;
3845
- this.#resumeURL = undefined;
3846
- }
3847
- this.#emit("invalidSession", resumable);
3848
- ws.close(resumable ? GatewayCloseCodes.UnknownError : 1000, resumable ? "Invalid session (resumable)" : "Invalid session");
3849
- break;
5042
+ this.#heartbeat.sendHeartbeat();
5043
+ return;
5044
+ case "heartbeat-ack":
5045
+ this.#heartbeat.acknowledge();
5046
+ this.ping = this.#heartbeat.latency;
5047
+ this.#emit("heartbeatAck", action.data);
5048
+ return;
5049
+ case "reconnect":
5050
+ this.#transport.close(1001, "Server requested reconnect");
5051
+ return;
5052
+ case "invalid-session": {
5053
+ if (!action.resumable)
5054
+ this.#session.invalidate(token, "invalid-session");
5055
+ this.#emit("invalidSession", action.resumable);
5056
+ this.#transport.close(action.resumable ? GatewayCloseCodes.UnknownError : 1000, action.resumable ? "Invalid session (resumable)" : "Invalid session");
5057
+ return;
3850
5058
  }
5059
+ case "invalid":
5060
+ this.#emitError(new GatewayError(action.message, undefined, action.cause === undefined ? undefined : { cause: action.cause }));
5061
+ this.#transport.close(1002, action.violation === "invalid-json" ? "Invalid JSON" : action.violation === "invalid-hello" ? "Invalid heartbeat interval" : "Invalid payload");
5062
+ return;
5063
+ case "unknown":
5064
+ return;
3851
5065
  }
3852
5066
  }
3853
- #handleDispatch(event, data) {
5067
+ #handleDispatch(event, data, token) {
3854
5068
  if (event === "READY") {
3855
- const ready = data;
3856
- if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string") {
3857
- this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
5069
+ if (!this.#session.activate(data, token)) {
5070
+ if (this.#session.owns(token))
5071
+ this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
3858
5072
  return;
3859
5073
  }
3860
- this.#sessionId = ready.session_id;
3861
- this.#resumeURL = ready.resume_gateway_url;
3862
- this.#attempt = 0;
5074
+ this.#reconnect.reset();
3863
5075
  this.#setState("READY" /* Ready */);
3864
5076
  this.#emit("ready", data);
3865
5077
  } else if (event === "RESUMED") {
3866
- this.#attempt = 0;
5078
+ this.#reconnect.reset();
3867
5079
  this.#setState("READY" /* Ready */);
3868
5080
  this.#emit("resumed", data);
3869
5081
  }
@@ -3872,183 +5084,60 @@ class Gateway {
3872
5084
  return;
3873
5085
  this.#emit(event ?? "dispatch", data);
3874
5086
  }
3875
- #handleHello(data) {
3876
- const interval = data?.heartbeat_interval;
3877
- if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0) {
3878
- this.#emitError(new GatewayError("Gateway HELLO payload contains an invalid heartbeat interval"));
3879
- this.#ws?.close(1002, "Invalid heartbeat interval");
3880
- return;
3881
- }
3882
- this.#heartbeatInterval = interval;
3883
- this.#setState(this.#sessionId && this.#sequence !== null && this.#resumeURL ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
3884
- this.#startHeartbeat(interval);
5087
+ #handleHello(interval) {
5088
+ this.#setState(this.#session.canResume ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
5089
+ this.#heartbeat.start(interval);
3885
5090
  this.#identifyOrResume();
3886
5091
  }
3887
5092
  #identifyOrResume() {
3888
- if (this.#sessionId && this.#sequence !== null && this.#resumeURL) {
5093
+ const intent = this.#session.handshake();
5094
+ if (intent.type === "resume") {
3889
5095
  this.#setState("RESUME" /* Resume */);
3890
- this.send({
3891
- op: GatewayOpcodes.Resume,
3892
- d: {
3893
- token: this.#options.token,
3894
- session_id: this.#sessionId,
3895
- seq: this.#sequence
3896
- },
3897
- s: null,
3898
- t: null
3899
- });
5096
+ this.#dispatch(resumePayload(this.#options.token, intent), true);
3900
5097
  return;
3901
5098
  }
3902
5099
  this.#setState("IDENTIFY" /* Identify */);
3903
- const props = this.#options.properties ?? {
3904
- os: "Android",
3905
- browser: "Discord Android",
3906
- device: "Discord Android"
3907
- };
3908
- const os = props.os ?? "Android";
3909
- const browser = props.browser ?? "Discord Android";
3910
- const device = props.device ?? "Discord Android";
3911
- this.send({
3912
- op: GatewayOpcodes.Identify,
3913
- d: {
3914
- token: this.#options.token,
3915
- intents: this.#options.intents,
3916
- properties: {
3917
- os,
3918
- browser,
3919
- device,
3920
- $os: os,
3921
- $browser: browser,
3922
- $device: device,
3923
- ...props
3924
- },
3925
- presence: {
3926
- since: this.#options.presence?.since ?? null,
3927
- activities: this.#options.presence?.activities ?? [],
3928
- status: this.#options.presence?.status ?? "online",
3929
- afk: Boolean(this.#options.presence?.afk)
3930
- },
3931
- shard: [this.#options.shardId, this.#options.shardCount]
3932
- },
3933
- s: null,
3934
- t: null
3935
- });
3936
- }
3937
- #startHeartbeat(interval) {
3938
- this.#clearHeartbeatTimers();
3939
- this.#heartbeatACK = true;
3940
- this.#initialHeartbeat = setTimeout(() => this.#sendHeartbeat(), Math.random() * interval);
3941
- this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), interval);
3942
- }
3943
- #sendHeartbeat() {
3944
- this.#heartbeatACK = false;
3945
- this.#heartbeatSentAt = Date.now();
3946
- if (!this.#dispatch({
3947
- op: GatewayOpcodes.Heartbeat,
3948
- d: this.#sequence,
3949
- s: null,
3950
- t: null
3951
- }, true)) {
3952
- this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
3953
- return;
3954
- }
3955
- this.#clearHeartbeatAckTimer();
3956
- this.#heartbeatAckTimer = setTimeout(() => {
3957
- if (!this.#heartbeatACK) {
3958
- const elapsed = Date.now() - this.#heartbeatSentAt;
3959
- this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${elapsed}ms.`));
3960
- try {
3961
- this.#ws?.close(1001, "Heartbeat timeout");
3962
- } catch (error) {
3963
- this.#emitError(error);
3964
- }
3965
- }
3966
- }, this.#options.heartbeatAckTimeout);
5100
+ this.#dispatch(identifyPayload({
5101
+ token: this.#options.token,
5102
+ intents: this.#options.intents,
5103
+ shardId: this.#options.shardId,
5104
+ shardCount: this.#options.shardCount,
5105
+ properties: this.#options.properties,
5106
+ presence: this.#options.presence
5107
+ }), true);
3967
5108
  }
3968
- #close(ws, code) {
3969
- if (this.#ws !== ws)
3970
- return;
3971
- this.#ws = undefined;
5109
+ #close(code, token) {
3972
5110
  this.#clearTimers();
3973
- const action = this.#closeAction(code);
5111
+ const action = this.#reconnect.classifyClose(code, this.#session.canResume);
3974
5112
  this.#emit("close", { code, action });
3975
- if (this.#closed || !this.#options.reconnect || action === "stop") {
5113
+ if (this.#closed || !this.#reconnect.enabled || action === "stop") {
5114
+ if (action === "stop")
5115
+ this.#closed = true;
3976
5116
  this.#setState(this.#closed ? "CLOSED" /* Closed */ : "CONNECT" /* Connect */);
3977
- if (this.#connectPromise)
3978
- this.#settleConnect(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
5117
+ if (this.#reconnect.connecting)
5118
+ this.#reconnect.settle(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
3979
5119
  return;
3980
5120
  }
3981
- if (action === "identify") {
3982
- this.#sequence = null;
3983
- this.#sessionId = undefined;
3984
- this.#resumeURL = undefined;
3985
- }
5121
+ if (action === "identify")
5122
+ this.#session.invalidate(token, "session-timeout");
3986
5123
  this.#setState("RECONNECT" /* Reconnect */);
3987
5124
  this.#scheduleReconnect();
3988
5125
  }
3989
- #closeAction(code) {
3990
- const fatal = [
3991
- GatewayCloseCodes.AuthenticationFailed,
3992
- GatewayCloseCodes.InvalidShard,
3993
- GatewayCloseCodes.ShardingRequired,
3994
- GatewayCloseCodes.InvalidAPIVersion,
3995
- GatewayCloseCodes.InvalidIntents,
3996
- GatewayCloseCodes.DisallowedIntents
3997
- ];
3998
- if (fatal.includes(code))
3999
- return "stop";
4000
- if (code === GatewayCloseCodes.InvalidSeq || code === GatewayCloseCodes.SessionTimedOut)
4001
- return "identify";
4002
- return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
4003
- }
4004
5126
  #scheduleReconnect() {
4005
- if (this.#closed || !this.#options.reconnect || this.#reconnectTimer)
5127
+ if (this.#closed)
4006
5128
  return;
4007
- if (this.#attempt >= this.#options.maxReconnectAttempts) {
4008
- this.#settleConnect(new GatewayError("Gateway reconnect attempts exhausted."));
4009
- this.#setState("CONNECT" /* Connect */);
5129
+ const result = this.#reconnect.schedule(() => this.#open(this.#session.connectURL(DEFAULT_GATEWAY_URL)));
5130
+ if (result.scheduled || result.reason !== "exhausted")
4010
5131
  return;
4011
- }
4012
- const delay = Math.min(this.#options.reconnectMaxDelay, this.#options.reconnectBaseDelay * 2 ** this.#attempt++);
4013
- const jitter = Math.random() * Math.max(1, delay * 0.25);
4014
- this.#reconnectTimer = setTimeout(() => {
4015
- this.#reconnectTimer = undefined;
4016
- this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
4017
- }, delay + jitter);
5132
+ this.#reconnect.settle(new GatewayError("Gateway reconnect attempts exhausted."));
5133
+ this.#setState("CONNECT" /* Connect */);
4018
5134
  }
4019
5135
  #settleConnect(error) {
4020
- const resolve = this.#resolveConnect;
4021
- const reject = this.#rejectConnect;
4022
- this.#resolveConnect = undefined;
4023
- this.#rejectConnect = undefined;
4024
- this.#connectPromise = undefined;
4025
- if (error)
4026
- reject?.(error);
4027
- else
4028
- resolve?.();
4029
- }
4030
- #clearHeartbeatAckTimer() {
4031
- if (this.#heartbeatAckTimer)
4032
- clearTimeout(this.#heartbeatAckTimer);
4033
- this.#heartbeatAckTimer = undefined;
4034
- }
4035
- #clearHeartbeatTimers() {
4036
- if (this.#heartbeatTimer)
4037
- clearInterval(this.#heartbeatTimer);
4038
- if (this.#initialHeartbeat)
4039
- clearTimeout(this.#initialHeartbeat);
4040
- this.#clearHeartbeatAckTimer();
4041
- this.#heartbeatTimer = undefined;
4042
- this.#initialHeartbeat = undefined;
5136
+ this.#reconnect.settle(error);
4043
5137
  }
4044
5138
  #clearTimers() {
4045
- if (this.#reconnectTimer)
4046
- clearTimeout(this.#reconnectTimer);
4047
- if (this.#zombieTimer)
4048
- clearInterval(this.#zombieTimer);
4049
- this.#reconnectTimer = undefined;
4050
- this.#zombieTimer = undefined;
4051
- this.#clearHeartbeatTimers();
5139
+ this.#reconnect.cancel();
5140
+ this.#heartbeat.stop();
4052
5141
  }
4053
5142
  #normalizeError(error) {
4054
5143
  return error instanceof GatewayError ? error : new GatewayError("Gateway connection failed.", undefined, {
@@ -4080,37 +5169,6 @@ class Gateway {
4080
5169
  this.state = next;
4081
5170
  this.#emit("stateChange", { previous, next });
4082
5171
  }
4083
- #initDecompressor() {
4084
- const ds = new DecompressionStream("deflate-raw");
4085
- this.#decompressor = {
4086
- writer: ds.writable.getWriter(),
4087
- reader: ds.readable.getReader()
4088
- };
4089
- }
4090
- async#decompress(chunk) {
4091
- if (!this.#decompressor)
4092
- this.#initDecompressor();
4093
- const { writer, reader } = this.#decompressor;
4094
- await writer.write(chunk);
4095
- const parts = [];
4096
- while (true) {
4097
- const { done, value } = await Promise.race([
4098
- reader.read(),
4099
- new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), 50))
4100
- ]);
4101
- if (done || !value)
4102
- break;
4103
- parts.push(value);
4104
- }
4105
- const total = parts.reduce((n, p) => n + p.length, 0);
4106
- const merged = new Uint8Array(total);
4107
- let offset = 0;
4108
- for (const p of parts) {
4109
- merged.set(p, offset);
4110
- offset += p.length;
4111
- }
4112
- return new TextDecoder().decode(merged);
4113
- }
4114
5172
  }
4115
5173
 
4116
5174
  // packages/core/src/index.ts
@@ -4181,6 +5239,7 @@ class Client extends EventEmitter2 {
4181
5239
  users;
4182
5240
  guilds;
4183
5241
  channels;
5242
+ stageInstances;
4184
5243
  application;
4185
5244
  get ws() {
4186
5245
  return this.#gateway;
@@ -4223,14 +5282,15 @@ class Client extends EventEmitter2 {
4223
5282
  this.users = new UserManager(this.rest);
4224
5283
  this.guilds = new GuildManager(this.rest);
4225
5284
  this.channels = new ChannelManager(this.rest);
5285
+ this.stageInstances = new StageInstanceManager(this.rest);
4226
5286
  const placeholderAppCommands = new ApplicationCommandManager(this.rest, "0");
4227
5287
  this.application = { commands: placeholderAppCommands };
4228
5288
  this.#resourceContext = {
4229
- sendMessage: (channelId, options2) => this.channels.send(channelId, options2),
4230
- editMessage: (channelId, messageId, options2) => this.channels.editMessage(channelId, messageId, options2),
5289
+ sendMessage: (channelId, options) => this.channels.send(channelId, options),
5290
+ editMessage: (channelId, messageId, options) => this.channels.editMessage(channelId, messageId, options),
4231
5291
  deleteMessage: (channelId, messageId) => this.channels.deleteMessage(channelId, messageId),
4232
5292
  crosspostMessage: (channelId, messageId) => this.channels.crosspostMessage(channelId, messageId),
4233
- editChannel: (channelId, options2) => this.channels.edit(channelId, options2),
5293
+ editChannel: (channelId, options) => this.channels.edit(channelId, options),
4234
5294
  deleteChannel: (channelId) => this.channels.deleteChannel(channelId),
4235
5295
  addReaction: (channelId, messageId, emoji) => this.channels.addReaction(channelId, messageId, emoji),
4236
5296
  removeOwnReaction: (channelId, messageId, emoji) => this.channels.removeOwnReaction(channelId, messageId, emoji),
@@ -4302,7 +5362,7 @@ class Client extends EventEmitter2 {
4302
5362
  ...payload.channels ?? [],
4303
5363
  ...payload.threads ?? []
4304
5364
  ])
4305
- this.channels.set(channelData.id, new Channel(channelData, this.#resourceContext));
5365
+ this.channels.set(channelData.id, createChannel(channelData, this.#resourceContext));
4306
5366
  this.emit(wasCached ? "guildAvailable" /* GuildAvailable */ : "guildCreate" /* GuildCreate */, payload);
4307
5367
  });
4308
5368
  this.#gateway.on("GUILD_UPDATE", (data) => {
@@ -4349,12 +5409,12 @@ class Client extends EventEmitter2 {
4349
5409
  this.#gateway.on("AUTO_MODERATION_RULE_DELETE", (data) => this.emit("autoModerationRuleDelete" /* AutoModerationRuleDelete */, data));
4350
5410
  this.#gateway.on("AUTO_MODERATION_ACTION_EXECUTION", (data) => this.emit("autoModerationActionExecution" /* AutoModerationActionExecution */, data));
4351
5411
  this.#gateway.on("CHANNEL_CREATE", (data) => {
4352
- const channel = new Channel(data, this.#resourceContext);
5412
+ const channel = createChannel(data, this.#resourceContext);
4353
5413
  this.channels.update(channel);
4354
5414
  this.emit("channelCreate" /* ChannelCreate */, channel);
4355
5415
  });
4356
5416
  this.#gateway.on("CHANNEL_UPDATE", (data) => {
4357
- const channel = new Channel(data, this.#resourceContext);
5417
+ const channel = createChannel(data, this.#resourceContext);
4358
5418
  this.channels.update(channel);
4359
5419
  this.emit("channelUpdate" /* ChannelUpdate */, channel);
4360
5420
  });
@@ -4365,12 +5425,12 @@ class Client extends EventEmitter2 {
4365
5425
  });
4366
5426
  this.#gateway.on("CHANNEL_PINS_UPDATE", (data) => this.emit("channelPinsUpdate" /* ChannelPinsUpdate */, data));
4367
5427
  this.#gateway.on("THREAD_CREATE", (data) => {
4368
- const channel = new Channel(data, this.#resourceContext);
5428
+ const channel = createChannel(data, this.#resourceContext);
4369
5429
  this.channels.update(channel);
4370
5430
  this.emit("threadCreate" /* ThreadCreate */, channel);
4371
5431
  });
4372
5432
  this.#gateway.on("THREAD_UPDATE", (data) => {
4373
- const channel = new Channel(data, this.#resourceContext);
5433
+ const channel = createChannel(data, this.#resourceContext);
4374
5434
  this.channels.update(channel);
4375
5435
  this.emit("threadUpdate" /* ThreadUpdate */, channel);
4376
5436
  });
@@ -4450,7 +5510,7 @@ class Client extends EventEmitter2 {
4450
5510
  if (options?.guildScheduledEventId)
4451
5511
  query.set("guild_scheduled_event_id", options.guildScheduledEventId);
4452
5512
  const qs = query.toString();
4453
- return this.rest.get(`/invites/${code}${qs ? `?${qs}` : ""}`);
5513
+ return this.rest.get(`/invites/${encodeURIComponent(code)}${qs ? `?${qs}` : ""}`);
4454
5514
  }
4455
5515
  fetchSticker(id) {
4456
5516
  return this.rest.get(`/stickers/${id}`);
@@ -4459,14 +5519,14 @@ class Client extends EventEmitter2 {
4459
5519
  return this.rest.get(`/sticker-packs`);
4460
5520
  }
4461
5521
  fetchGuildTemplate(code) {
4462
- return this.rest.get(`/guilds/templates/${code}`);
5522
+ return this.rest.get(`/guilds/templates/${encodeURIComponent(code)}`);
4463
5523
  }
4464
5524
  generateInvite(options) {
4465
5525
  if (!this.user)
4466
5526
  throw new Error("Client must be ready to generate invite.");
4467
5527
  const params = new URLSearchParams({
4468
5528
  client_id: this.user.id,
4469
- scopes: (options?.scopes ?? ["bot"]).join(" ")
5529
+ scope: (options?.scopes ?? ["bot"]).join(" ")
4470
5530
  });
4471
5531
  if (options?.permissions) {
4472
5532
  params.set("permissions", String(options.permissions));
@@ -4588,13 +5648,17 @@ class HandlerRegistry {
4588
5648
  }
4589
5649
  }
4590
5650
  // packages/sharding/src/bus.ts
5651
+ var REPLY_TYPE = "__lunibee:reply";
5652
+
4591
5653
  class ShardBus {
4592
5654
  #channel;
4593
5655
  #handlers = new Map;
5656
+ #errorHandlers = new Set;
4594
5657
  #shardId;
4595
5658
  #namespace;
4596
5659
  channelName;
4597
5660
  #counter = 0;
5661
+ #pending = new Map;
4598
5662
  constructor(shardId, channelName) {
4599
5663
  if (!Number.isInteger(shardId) || shardId < 0)
4600
5664
  throw new RangeError("Shard ID must be a non-negative integer.");
@@ -4619,6 +5683,16 @@ class ShardBus {
4619
5683
  this.#handlers.get(type)?.delete(handler);
4620
5684
  return this;
4621
5685
  }
5686
+ onError(handler) {
5687
+ if (typeof handler !== "function")
5688
+ throw new TypeError("Shard bus error handler is required.");
5689
+ this.#errorHandlers.add(handler);
5690
+ return this;
5691
+ }
5692
+ offError(handler) {
5693
+ this.#errorHandlers.delete(handler);
5694
+ return this;
5695
+ }
4622
5696
  send(target, type, data) {
4623
5697
  if (!Number.isInteger(target) || target < 0)
4624
5698
  throw new RangeError("Shard target must be a non-negative integer.");
@@ -4627,11 +5701,56 @@ class ShardBus {
4627
5701
  broadcast(type, data) {
4628
5702
  return this.#publish(null, type, data);
4629
5703
  }
5704
+ respond(type, handler) {
5705
+ return this.on(type, async (message) => {
5706
+ if (!message.expectsReply)
5707
+ return;
5708
+ const reply = { requestId: message.id };
5709
+ try {
5710
+ reply.result = await handler(message.data, message);
5711
+ } catch (error) {
5712
+ reply.error = error instanceof Error ? error.message : String(error);
5713
+ }
5714
+ this.#publish(message.source, REPLY_TYPE, reply);
5715
+ });
5716
+ }
5717
+ async request(target, type, data, timeoutMs = 5000) {
5718
+ if (!Number.isInteger(target) || target < 0)
5719
+ throw new RangeError("Shard target must be a non-negative integer.");
5720
+ const [reply] = await this.#collect(target, type, data, timeoutMs, 1);
5721
+ if (!reply)
5722
+ throw new Error(`Shard ${target} did not reply to "${type}" within ${timeoutMs}ms.`);
5723
+ if (reply.error !== undefined)
5724
+ throw new Error(reply.error);
5725
+ return reply.result;
5726
+ }
5727
+ broadcastRequest(type, data, options = {}) {
5728
+ return this.#collect(null, type, data, options.timeoutMs ?? 5000, options.expected ?? Number.POSITIVE_INFINITY);
5729
+ }
5730
+ #collect(target, type, data, timeoutMs, expected) {
5731
+ const replies = [];
5732
+ return new Promise((resolve) => {
5733
+ let requestId = "";
5734
+ const finish = () => {
5735
+ clearTimeout(timer);
5736
+ this.#pending.delete(requestId);
5737
+ resolve(replies);
5738
+ };
5739
+ const timer = setTimeout(finish, timeoutMs);
5740
+ requestId = this.#publish(target, type, data, true);
5741
+ this.#pending.set(requestId, (reply) => {
5742
+ replies.push(reply);
5743
+ if (replies.length >= expected)
5744
+ finish();
5745
+ });
5746
+ });
5747
+ }
4630
5748
  close() {
4631
5749
  this.#channel.close();
4632
5750
  this.#handlers.clear();
5751
+ this.#errorHandlers.clear();
4633
5752
  }
4634
- #publish(target, type, data) {
5753
+ #publish(target, type, data, expectsReply = false) {
4635
5754
  if (!type.trim())
4636
5755
  throw new TypeError("Shard message type is required.");
4637
5756
  const id = `${this.#namespace}:${this.#shardId}:${++this.#counter}`;
@@ -4640,7 +5759,8 @@ class ShardBus {
4640
5759
  target,
4641
5760
  type,
4642
5761
  data,
4643
- id
5762
+ id,
5763
+ ...expectsReply ? { expectsReply } : {}
4644
5764
  });
4645
5765
  return id;
4646
5766
  }
@@ -4649,13 +5769,28 @@ class ShardBus {
4649
5769
  return;
4650
5770
  if (message.source === this.#shardId || message.target !== null && message.target !== this.#shardId)
4651
5771
  return;
5772
+ if (message.type === REPLY_TYPE) {
5773
+ const reply = message.data;
5774
+ this.#pending.get(reply?.requestId)?.({
5775
+ shardId: message.source,
5776
+ ...reply.error !== undefined ? { error: reply.error } : { result: reply.result }
5777
+ });
5778
+ return;
5779
+ }
4652
5780
  for (const handler of this.#handlers.get(message.type) ?? []) {
4653
5781
  try {
4654
5782
  const result = handler(message);
4655
5783
  if (result && typeof result.then === "function")
4656
- Promise.resolve(result).catch(() => {
4657
- return;
4658
- });
5784
+ Promise.resolve(result).catch((error) => this.#reportError(error, message));
5785
+ } catch (error) {
5786
+ this.#reportError(error, message);
5787
+ }
5788
+ }
5789
+ }
5790
+ #reportError(error, message) {
5791
+ for (const handler of this.#errorHandlers) {
5792
+ try {
5793
+ handler(error, message);
4659
5794
  } catch {}
4660
5795
  }
4661
5796
  }
@@ -4663,7 +5798,7 @@ class ShardBus {
4663
5798
  // packages/sharding/src/cluster.ts
4664
5799
  import { fork } from "child_process";
4665
5800
  import { cpus } from "os";
4666
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5801
+ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4667
5802
 
4668
5803
  class ClusterManager {
4669
5804
  clusters = new Map;
@@ -4672,6 +5807,8 @@ class ClusterManager {
4672
5807
  #auto;
4673
5808
  #autoScaleTimer;
4674
5809
  #spawned = false;
5810
+ #stopping = false;
5811
+ #restartTimers = new Set;
4675
5812
  constructor(options) {
4676
5813
  if (!options.token?.trim())
4677
5814
  throw new TypeError("A Discord bot token is required.");
@@ -4716,20 +5853,8 @@ class ClusterManager {
4716
5853
  for (let i = 0;i < clusterCount; i++) {
4717
5854
  if (chunks[i].length === 0)
4718
5855
  continue;
4719
- const child = fork(this.#options.script, [], {
4720
- env: {
4721
- ...process.env,
4722
- SHARD_LIST: chunks[i].join(","),
4723
- SHARD_COUNT: count.toString(),
4724
- CLUSTER_ID: i.toString()
4725
- }
4726
- });
4727
- this.clusters.set(i, {
4728
- id: i,
4729
- process: child,
4730
- shards: chunks[i]
4731
- });
4732
- await sleep(500);
5856
+ this.#launch(i, chunks[i], count);
5857
+ await sleep2(500);
4733
5858
  }
4734
5859
  if (this.#options.autoScaleInterval && this.#options.autoScaleInterval > 0) {
4735
5860
  this.#autoScaleTimer = setInterval(() => {
@@ -4737,6 +5862,37 @@ class ClusterManager {
4737
5862
  }, this.#options.autoScaleInterval);
4738
5863
  }
4739
5864
  }
5865
+ #launch(id, shards, shardCount) {
5866
+ const child = fork(this.#options.script, [], {
5867
+ env: {
5868
+ ...process.env,
5869
+ SHARD_LIST: shards.join(","),
5870
+ SHARD_COUNT: shardCount.toString(),
5871
+ CLUSTER_ID: id.toString()
5872
+ }
5873
+ });
5874
+ const info = { id, process: child, shards };
5875
+ this.clusters.set(id, info);
5876
+ child.once("exit", (code, signal) => {
5877
+ if (this.clusters.get(id)?.process !== child)
5878
+ return;
5879
+ try {
5880
+ this.#options.onClusterExit?.(info, code, signal);
5881
+ } catch {}
5882
+ if (this.#stopping || this.#options.restartOnExit === false)
5883
+ return;
5884
+ this.clusters.delete(id);
5885
+ const timer = setTimeout(() => {
5886
+ this.#restartTimers.delete(timer);
5887
+ if (this.#stopping || !this.#spawned)
5888
+ return;
5889
+ this.#launch(id, shards, shardCount);
5890
+ }, this.#options.restartDelay ?? 5000);
5891
+ timer.unref?.();
5892
+ this.#restartTimers.add(timer);
5893
+ });
5894
+ return info;
5895
+ }
4740
5896
  async checkAutoScale() {
4741
5897
  if (!this.#auto)
4742
5898
  return;
@@ -4757,6 +5913,8 @@ class ClusterManager {
4757
5913
  await this.spawn();
4758
5914
  }
4759
5915
  async shutdownAll(timeoutMs = this.#options.shutdownTimeout ?? 5000) {
5916
+ this.#stopping = true;
5917
+ this.#clearRestartTimers();
4760
5918
  if (this.#autoScaleTimer) {
4761
5919
  clearInterval(this.#autoScaleTimer);
4762
5920
  this.#autoScaleTimer = undefined;
@@ -4764,6 +5922,12 @@ class ClusterManager {
4764
5922
  await Promise.all([...this.clusters.values()].map((cluster) => this.#shutdownCluster(cluster, timeoutMs)));
4765
5923
  this.clusters.clear();
4766
5924
  this.#spawned = false;
5925
+ this.#stopping = false;
5926
+ }
5927
+ #clearRestartTimers() {
5928
+ for (const timer of this.#restartTimers)
5929
+ clearTimeout(timer);
5930
+ this.#restartTimers.clear();
4767
5931
  }
4768
5932
  async#shutdownCluster(cluster, timeoutMs) {
4769
5933
  const child = cluster.process;
@@ -4792,6 +5956,8 @@ class ClusterManager {
4792
5956
  });
4793
5957
  }
4794
5958
  killAll() {
5959
+ this.#stopping = true;
5960
+ this.#clearRestartTimers();
4795
5961
  if (this.#autoScaleTimer) {
4796
5962
  clearInterval(this.#autoScaleTimer);
4797
5963
  this.#autoScaleTimer = undefined;
@@ -4803,13 +5969,15 @@ class ClusterManager {
4803
5969
  }
4804
5970
  this.clusters.clear();
4805
5971
  this.#spawned = false;
5972
+ this.#stopping = false;
4806
5973
  }
4807
5974
  }
4808
5975
 
4809
5976
  // packages/sharding/src/index.ts
4810
- var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5977
+ var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4811
5978
 
4812
5979
  class ShardManager {
5980
+ static IDENTIFY_INTERVAL = 5000;
4813
5981
  shards = new Map;
4814
5982
  get shardCount() {
4815
5983
  return this.shards.size;
@@ -4829,8 +5997,13 @@ class ShardManager {
4829
5997
  throw new RangeError("Shard count must be a positive integer.");
4830
5998
  if (options.autoScaleInterval !== undefined && (!Number.isInteger(options.autoScaleInterval) || options.autoScaleInterval < 1000))
4831
5999
  throw new RangeError("autoScaleInterval must be an integer of at least 1000 milliseconds.");
6000
+ if (options.spawnDelay !== undefined && (!Number.isFinite(options.spawnDelay) || options.spawnDelay < 0))
6001
+ throw new RangeError("spawnDelay must be a non-negative finite number of milliseconds.");
4832
6002
  this.#auto = options.shardCount === "auto";
4833
- this.#options = { ...options };
6003
+ this.#options = {
6004
+ spawnDelay: ShardManager.IDENTIFY_INTERVAL,
6005
+ ...options
6006
+ };
4834
6007
  if (options.shardCount !== "auto")
4835
6008
  this.#initialize(count);
4836
6009
  }
@@ -4864,7 +6037,7 @@ class ShardManager {
4864
6037
  });
4865
6038
  }
4866
6039
  if (id + 1 < this.shardCount && this.#options.spawnDelay && this.#options.spawnDelay > 0)
4867
- await sleep2(this.#options.spawnDelay);
6040
+ await sleep3(this.#options.spawnDelay);
4868
6041
  }
4869
6042
  if (this.#options.autoScaleInterval && !this.#autoScaleTimer) {
4870
6043
  this.#autoScaleTimer = setInterval(() => {
@@ -4936,7 +6109,7 @@ class ShardManager {
4936
6109
  }
4937
6110
  }
4938
6111
  // packages/utils/src/index.ts
4939
- function sleep3(ms) {
6112
+ function sleep4(ms) {
4940
6113
  return new Promise((resolve) => setTimeout(resolve, ms));
4941
6114
  }
4942
6115
  function randomInt(min, max) {
@@ -4947,11 +6120,11 @@ function isSnowflake(value) {
4947
6120
  }
4948
6121
  // packages/voice/src/index.ts
4949
6122
  var VoiceConnectionState;
4950
- ((VoiceConnectionState2) => {
4951
- VoiceConnectionState2["Disconnected"] = "disconnected";
4952
- VoiceConnectionState2["Connecting"] = "connecting";
4953
- VoiceConnectionState2["Connected"] = "connected";
4954
- VoiceConnectionState2["Destroyed"] = "destroyed";
6123
+ ((VoiceConnectionState) => {
6124
+ VoiceConnectionState["Disconnected"] = "disconnected";
6125
+ VoiceConnectionState["Connecting"] = "connecting";
6126
+ VoiceConnectionState["Connected"] = "connected";
6127
+ VoiceConnectionState["Destroyed"] = "destroyed";
4955
6128
  })(VoiceConnectionState ||= {});
4956
6129
 
4957
6130
  class VoiceError extends Error {
@@ -5293,6 +6466,52 @@ class VoiceReceiver {
5293
6466
  }
5294
6467
  }
5295
6468
  }
6469
+ var voiceConnections = new Map;
6470
+ function joinVoiceChannel(options) {
6471
+ let connection = voiceConnections.get(options.guildId);
6472
+ if (!connection) {
6473
+ connection = new VoiceConnection(options);
6474
+ const created = connection;
6475
+ created.on("stateChange", (next) => {
6476
+ if (next === "destroyed" /* Destroyed */ && voiceConnections.get(created.guildId) === created)
6477
+ voiceConnections.delete(created.guildId);
6478
+ });
6479
+ voiceConnections.set(options.guildId, connection);
6480
+ }
6481
+ connection.setChannel(options.channelId);
6482
+ connection.setSuppression(options);
6483
+ connection.connect();
6484
+ return connection;
6485
+ }
6486
+ function getVoiceConnection(guildId) {
6487
+ return voiceConnections.get(guildId);
6488
+ }
6489
+ function createAudioPlayer() {
6490
+ return new AudioPlayer;
6491
+ }
6492
+ function createAudioResource(stream, metadata) {
6493
+ return new AudioStream(stream, metadata);
6494
+ }
6495
+ function entersState(target, status, timeoutMs) {
6496
+ if (target.state === status)
6497
+ return Promise.resolve(target);
6498
+ const emitter = target;
6499
+ const isConnection = target instanceof VoiceConnection;
6500
+ return new Promise((resolve, reject) => {
6501
+ const onChange = (first, second) => {
6502
+ if ((isConnection ? first : second) !== status)
6503
+ return;
6504
+ clearTimeout(timer);
6505
+ emitter.off("stateChange", onChange);
6506
+ resolve(target);
6507
+ };
6508
+ const timer = setTimeout(() => {
6509
+ emitter.off("stateChange", onChange);
6510
+ reject(new VoiceError(`Did not enter state "${String(status)}" within ${timeoutMs}ms.`));
6511
+ }, timeoutMs);
6512
+ emitter.on("stateChange", onChange);
6513
+ });
6514
+ }
5296
6515
  // packages/builders/src/embed.ts
5297
6516
  class EmbedBuilder {
5298
6517
  #data = {};
@@ -5531,9 +6750,9 @@ class ButtonBuilder {
5531
6750
  }
5532
6751
  setURL(value) {
5533
6752
  validateText(value, 512, "Button URL");
5534
- let url2;
6753
+ let url;
5535
6754
  try {
5536
- url2 = new URL(value);
6755
+ url = new URL(value);
5537
6756
  } catch (error) {
5538
6757
  throw new TypeError("Button URL must be a valid URL.", {
5539
6758
  cause: error
@@ -5541,7 +6760,7 @@ class ButtonBuilder {
5541
6760
  }
5542
6761
  this.#data.style = ButtonStyle.Link;
5543
6762
  delete this.#data.custom_id;
5544
- this.#data.url = url2.toString();
6763
+ this.#data.url = url.toString();
5545
6764
  return this;
5546
6765
  }
5547
6766
  setEmoji(emoji) {
@@ -5698,8 +6917,8 @@ class ContainerBuilder {
5698
6917
  this.#accentColor = color;
5699
6918
  return this;
5700
6919
  }
5701
- setSpoiler(spoiler2 = true) {
5702
- this.#spoiler = spoiler2;
6920
+ setSpoiler(spoiler = true) {
6921
+ this.#spoiler = spoiler;
5703
6922
  return this;
5704
6923
  }
5705
6924
  toJSON() {
@@ -5775,13 +6994,13 @@ class MediaGalleryBuilder {
5775
6994
  class FileComponentBuilder {
5776
6995
  #url = "";
5777
6996
  #spoiler;
5778
- setUrl(url2) {
5779
- validateText(url2, 2048, "File component URL");
5780
- this.#url = url2;
6997
+ setUrl(url) {
6998
+ validateText(url, 2048, "File component URL");
6999
+ this.#url = url;
5781
7000
  return this;
5782
7001
  }
5783
- setSpoiler(spoiler2 = true) {
5784
- this.#spoiler = spoiler2;
7002
+ setSpoiler(spoiler = true) {
7003
+ this.#spoiler = spoiler;
5785
7004
  return this;
5786
7005
  }
5787
7006
  toJSON() {
@@ -5824,9 +7043,9 @@ class ThumbnailBuilder {
5824
7043
  #url = "";
5825
7044
  #description;
5826
7045
  #spoiler;
5827
- setUrl(url2) {
5828
- validateText(url2, 2048, "Thumbnail URL");
5829
- this.#url = url2;
7046
+ setUrl(url) {
7047
+ validateText(url, 2048, "Thumbnail URL");
7048
+ this.#url = url;
5830
7049
  return this;
5831
7050
  }
5832
7051
  setDescription(description) {
@@ -5834,8 +7053,8 @@ class ThumbnailBuilder {
5834
7053
  this.#description = description;
5835
7054
  return this;
5836
7055
  }
5837
- setSpoiler(spoiler2 = true) {
5838
- this.#spoiler = spoiler2;
7056
+ setSpoiler(spoiler = true) {
7057
+ this.#spoiler = spoiler;
5839
7058
  return this;
5840
7059
  }
5841
7060
  toJSON() {
@@ -5881,6 +7100,29 @@ function validateLength(value, field, max = 4000) {
5881
7100
  if (!Number.isInteger(value) || value < 0 || value > max)
5882
7101
  throw new RangeError(`${field} must be an integer between 0 and ${max}.`);
5883
7102
  }
7103
+ class UserSelectMenuBuilder extends EntitySelectBuilder {
7104
+ constructor() {
7105
+ super(ComponentType.UserSelect);
7106
+ }
7107
+ }
7108
+
7109
+ class RoleSelectMenuBuilder extends EntitySelectBuilder {
7110
+ constructor() {
7111
+ super(ComponentType.RoleSelect);
7112
+ }
7113
+ }
7114
+
7115
+ class MentionableSelectMenuBuilder extends EntitySelectBuilder {
7116
+ constructor() {
7117
+ super(ComponentType.MentionableSelect);
7118
+ }
7119
+ }
7120
+
7121
+ class ChannelSelectMenuBuilder extends EntitySelectBuilder {
7122
+ constructor() {
7123
+ super(ComponentType.ChannelSelect);
7124
+ }
7125
+ }
5884
7126
  // packages/builders/src/commands.ts
5885
7127
  var ApplicationCommandOptionType2 = {
5886
7128
  Subcommand: 1,
@@ -6239,9 +7481,15 @@ function validateNumberRange(value, minimum, maximum, field, integerOnly = true)
6239
7481
 
6240
7482
  class ContextMenuCommandBuilder {
6241
7483
  data;
6242
- constructor(type) {
7484
+ constructor(type = 2) {
6243
7485
  this.data = { type };
6244
7486
  }
7487
+ setType(type) {
7488
+ if (type !== 2 && type !== 3)
7489
+ throw new RangeError("Context menu command type must be 2 or 3.");
7490
+ this.data.type = type;
7491
+ return this;
7492
+ }
6245
7493
  setName(name) {
6246
7494
  if (typeof name !== "string" || name.trim().length < 1 || name.length > 32)
6247
7495
  throw new RangeError("Command name must contain 1-32 characters.");
@@ -6343,9 +7591,12 @@ export {
6343
7591
  ButtonBuilder,
6344
7592
  ButtonStyle,
6345
7593
  Cache,
7594
+ ResourceManager as CachedManager,
7595
+ CategoryChannel,
6346
7596
  Channel,
6347
7597
  ChannelManager,
6348
7598
  ChannelOptionBuilder,
7599
+ ChannelSelectMenuBuilder,
6349
7600
  ChannelType,
6350
7601
  Client,
6351
7602
  ClientEvent,
@@ -6358,113 +7609,165 @@ export {
6358
7609
  ComponentType,
6359
7610
  ContainerBuilder,
6360
7611
  ContentInventoryEntryBuilder,
7612
+ ContextMenuCommandBuilder,
7613
+ DMChannel,
6361
7614
  RESTError as DiscordAPIError,
6362
7615
  Embed,
6363
7616
  EmbedBuilder,
6364
7617
  Emoji,
6365
7618
  EmojiManager,
6366
7619
  EntitySelectBuilder,
7620
+ ClientEvent as Events,
7621
+ FATAL_CLOSE_CODES,
6367
7622
  FileComponentBuilder,
7623
+ ForumChannel,
6368
7624
  Gateway,
6369
7625
  GatewayCloseCodes,
6370
7626
  GatewayError,
7627
+ GatewayHeartbeat,
6371
7628
  GatewayIntentBits,
6372
7629
  GatewayOpcodes,
7630
+ GatewayReconnect,
7631
+ GatewaySession,
6373
7632
  GatewayState,
6374
7633
  Guild,
7634
+ GuildBanManager,
6375
7635
  GuildManager,
6376
7636
  GuildMember,
6377
7637
  GuildMemberManager,
6378
7638
  GuildOnboarding,
7639
+ GuildScheduledEventManager,
6379
7640
  GuildWelcomeScreen,
6380
7641
  HandlerRegistry,
7642
+ HttpTransport,
7643
+ IDENTIFY_CLOSE_CODES,
6381
7644
  IntegerOptionBuilder,
6382
7645
  IntentBits,
6383
7646
  Intents,
7647
+ IntentsBitField,
6384
7648
  Interaction,
6385
7649
  InteractionResponse,
6386
7650
  InteractionResponseType,
6387
7651
  InteractionType,
6388
7652
  Invite,
6389
7653
  Manager,
7654
+ MediaChannel,
6390
7655
  MediaGalleryBuilder,
6391
7656
  MemoryRateLimitStore,
6392
7657
  MentionableOptionBuilder,
7658
+ MentionableSelectMenuBuilder,
6393
7659
  Message,
6394
7660
  MessageCommandBuilder,
6395
7661
  MessageFlags,
6396
7662
  MessageManager,
6397
7663
  ModalBuilder,
6398
7664
  ModalSubmitInteraction,
7665
+ NewsChannel,
6399
7666
  NumberOptionBuilder,
6400
7667
  OnboardingPrompt,
6401
7668
  OnboardingPromptOption,
6402
7669
  Permission,
6403
7670
  PermissionFlagsBits,
7671
+ PermissionOverwriteManager,
6404
7672
  PermissionOverwriteType,
6405
7673
  PermissionSet,
6406
7674
  Permissions,
6407
7675
  PermissionsBitField,
7676
+ PlainTextDecoder,
6408
7677
  PremiumTier,
6409
7678
  REST,
6410
7679
  RESTError,
7680
+ RateLimiter,
6411
7681
  RedisRateLimitStore,
7682
+ RequestScheduler,
6412
7683
  ResourceManager,
7684
+ ResponseDecoder,
6413
7685
  Role,
6414
7686
  RoleManager,
6415
7687
  RoleOptionBuilder,
7688
+ RoleSelectMenuBuilder,
6416
7689
  Routes,
6417
7690
  SectionBuilder,
6418
7691
  SeparatorBuilder,
6419
7692
  ShardBus,
6420
7693
  ShardManager,
7694
+ ShardManager as ShardingManager,
6421
7695
  SlashCommandBuilder,
6422
7696
  SpeakingFlags,
7697
+ StageChannel,
7698
+ StageInstanceManager,
6423
7699
  GatewayState as Status,
6424
7700
  StickerFormatType,
6425
7701
  StickerType,
6426
7702
  StringOptionBuilder,
6427
7703
  StringSelectBuilder,
7704
+ StringSelectBuilder as StringSelectMenuBuilder,
6428
7705
  SubcommandBuilder,
6429
7706
  SubcommandGroupBuilder,
6430
7707
  TextChannel,
6431
7708
  TextDisplayBuilder,
6432
7709
  TextInputBuilder,
6433
7710
  TextInputStyle,
7711
+ ThreadChannel,
6434
7712
  ThreadManager,
6435
7713
  ThumbnailBuilder,
7714
+ TransportError,
6436
7715
  User,
6437
7716
  UserCommandBuilder,
6438
7717
  UserManager,
6439
7718
  UserOptionBuilder,
7719
+ UserSelectMenuBuilder,
6440
7720
  VerificationLevel,
7721
+ VoiceChannel,
6441
7722
  VoiceConnection,
6442
7723
  VoiceConnectionState,
7724
+ VoiceConnectionState as VoiceConnectionStatus,
6443
7725
  VoiceError,
6444
7726
  VoiceReceiver,
7727
+ WebSocketTransport,
6445
7728
  Webhook,
6446
7729
  WebhookClient,
6447
7730
  WebhookType,
6448
7731
  WelcomeScreenChannel,
7732
+ ZlibStreamDecoder,
7733
+ abortError,
6449
7734
  blockQuote,
6450
7735
  bold,
6451
7736
  bulletList,
6452
7737
  channelMention,
7738
+ classifyCloseCode,
7739
+ classifyFrame,
7740
+ classifyPayload,
6453
7741
  codeBlock,
7742
+ createAudioPlayer,
7743
+ createAudioResource,
7744
+ createChannel,
7745
+ createDecoder,
6454
7746
  createInteraction,
6455
7747
  createRetryPolicy,
7748
+ createRouteKey,
7749
+ entersState,
6456
7750
  escapeMarkdown,
7751
+ getVoiceConnection,
6457
7752
  heading,
7753
+ heartbeatPayload,
7754
+ identifyPayload,
6458
7755
  inlineCode,
6459
7756
  isSnowflake,
6460
7757
  italic,
7758
+ joinVoiceChannel,
6461
7759
  link,
7760
+ majorParameter,
6462
7761
  masked,
7762
+ normalizeRoutePath,
6463
7763
  orderedList,
6464
7764
  randomInt,
6465
7765
  resolveGatewayIntents,
7766
+ resumePayload,
6466
7767
  roleMention,
6467
- sleep3 as sleep,
7768
+ scopeBucket,
7769
+ serializeQuery,
7770
+ sleep4 as sleep,
6468
7771
  spoiler,
6469
7772
  strikethrough,
6470
7773
  subtext,
@@ -6473,4 +7776,4 @@ export {
6473
7776
  userMention
6474
7777
  };
6475
7778
 
6476
- //# debugId=CB787B7E0E4365B464756E2164756E21
7779
+ //# debugId=58F0E8EFC21C568E64756E2164756E21