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
@@ -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,44 @@ 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
+ // packages/types/src/index.ts
635
679
  var InteractionResponseType = {
636
680
  Pong: 1,
637
681
  ChannelMessage: 4,
@@ -927,7 +971,7 @@ class ResourceManager extends Manager {
927
971
  // packages/rest/package.json
928
972
  var package_default = {
929
973
  name: "@lunibee/rest",
930
- version: "0.1.7",
974
+ version: "0.1.8",
931
975
  type: "module",
932
976
  module: "./src/index.ts",
933
977
  types: "./src/index.ts",
@@ -952,6 +996,17 @@ class MemoryRateLimitStore {
952
996
  getBucket(key) {
953
997
  return this.#buckets.get(key);
954
998
  }
999
+ reserve(key) {
1000
+ const state = this.#buckets.get(key);
1001
+ const now = Date.now();
1002
+ if (!state || state.resetAt <= now)
1003
+ return { granted: true };
1004
+ if (state.remaining > 0) {
1005
+ state.remaining -= 1;
1006
+ return { granted: true };
1007
+ }
1008
+ return { granted: false, retryAfterMs: state.resetAt - now };
1009
+ }
955
1010
  updateBucket(key, state) {
956
1011
  this.#buckets.set(key, state);
957
1012
  const now = Date.now();
@@ -975,6 +1030,301 @@ class MemoryRateLimitStore {
975
1030
  }
976
1031
  }
977
1032
 
1033
+ // packages/rest/src/errors.ts
1034
+ class RESTError extends Error {
1035
+ status;
1036
+ code;
1037
+ errors;
1038
+ method;
1039
+ path;
1040
+ constructor(message, status, code, errors, options = {}) {
1041
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
1042
+ this.name = "RESTError";
1043
+ this.status = status;
1044
+ this.code = code;
1045
+ this.errors = errors;
1046
+ this.method = options.method;
1047
+ this.path = options.path;
1048
+ }
1049
+ }
1050
+ function abortError(path, reason) {
1051
+ return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
1052
+ }
1053
+ async function sleep(delay, signal, path) {
1054
+ if (delay <= 0)
1055
+ return;
1056
+ await new Promise((resolve, reject) => {
1057
+ const timer = setTimeout(resolve, delay);
1058
+ const abort = () => {
1059
+ clearTimeout(timer);
1060
+ reject(abortError(path, signal?.reason));
1061
+ };
1062
+ if (signal?.aborted)
1063
+ return abort();
1064
+ signal?.addEventListener("abort", abort, { once: true });
1065
+ });
1066
+ }
1067
+ async function abortable(promise, signal, path) {
1068
+ if (!signal)
1069
+ return promise;
1070
+ if (signal.aborted)
1071
+ throw abortError(path, signal.reason);
1072
+ return Promise.race([
1073
+ promise,
1074
+ new Promise((_, reject) => signal.addEventListener("abort", () => reject(abortError(path, signal.reason)), { once: true }))
1075
+ ]);
1076
+ }
1077
+
1078
+ // packages/rest/src/route.ts
1079
+ function normalizeRoutePath(path) {
1080
+ const queryIndex = path.indexOf("?");
1081
+ const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
1082
+ return routePath.replace(/\/\d+(?=\/|$)/g, "/:id");
1083
+ }
1084
+ function majorParameter(path) {
1085
+ const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/.exec(path);
1086
+ if (!match)
1087
+ return "@none";
1088
+ if (match[1] === "webhooks" && match[3] !== undefined)
1089
+ return `${match[2]}:${match[3]}`;
1090
+ return match[2];
1091
+ }
1092
+ function scopeBucket(hashOrRoute, major) {
1093
+ return `${hashOrRoute}|${major}`;
1094
+ }
1095
+ function createRouteKey(method, path) {
1096
+ const normalizedMethod = method.toUpperCase();
1097
+ return {
1098
+ method: normalizedMethod,
1099
+ path,
1100
+ route: `${normalizedMethod}:${normalizeRoutePath(path)}`,
1101
+ major: majorParameter(path)
1102
+ };
1103
+ }
1104
+ function serializeQuery(query) {
1105
+ if (query === undefined)
1106
+ return "";
1107
+ if (typeof query === "string")
1108
+ return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
1109
+ const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
1110
+ const serialized = params.toString();
1111
+ return serialized.length === 0 ? "" : `?${serialized}`;
1112
+ }
1113
+
1114
+ // packages/rest/src/scheduler.ts
1115
+ class RequestScheduler {
1116
+ static MAX_REMAPS = 2;
1117
+ #queues = new Map;
1118
+ get size() {
1119
+ return this.#queues.size;
1120
+ }
1121
+ async runResolved(resolveKey, signal, path, task) {
1122
+ let key = await resolveKey();
1123
+ for (let hop = 0;hop < RequestScheduler.MAX_REMAPS; hop++) {
1124
+ const outcome = await this.run(key, signal, path, async (release) => {
1125
+ const current = await resolveKey();
1126
+ if (current !== key)
1127
+ return { done: false, key: current };
1128
+ return { done: true, value: await task(key, release) };
1129
+ });
1130
+ if (outcome.done)
1131
+ return outcome.value;
1132
+ key = outcome.key;
1133
+ }
1134
+ return this.run(key, signal, path, (release) => task(key, release));
1135
+ }
1136
+ async run(key, signal, path, task) {
1137
+ const previous = this.#queues.get(key) ?? Promise.resolve();
1138
+ let open;
1139
+ const gate = new Promise((resolve) => {
1140
+ open = resolve;
1141
+ });
1142
+ this.#queues.set(key, gate);
1143
+ let released = false;
1144
+ const release = () => {
1145
+ if (released)
1146
+ return;
1147
+ released = true;
1148
+ open();
1149
+ if (this.#queues.get(key) === gate)
1150
+ this.#queues.delete(key);
1151
+ };
1152
+ try {
1153
+ await abortable(previous, signal, path);
1154
+ return await task(release);
1155
+ } finally {
1156
+ release();
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ // packages/rest/src/limiter.ts
1162
+ class RateLimiter {
1163
+ #store;
1164
+ #concurrent;
1165
+ constructor(store, options = {}) {
1166
+ this.#store = store;
1167
+ this.#concurrent = options.concurrent ?? false;
1168
+ }
1169
+ get store() {
1170
+ return this.#store;
1171
+ }
1172
+ async resolveBucketKey(route, major) {
1173
+ return scopeBucket(await this.#store.getBucketHash(route) ?? route, major);
1174
+ }
1175
+ get reserves() {
1176
+ return typeof this.#store.reserve === "function";
1177
+ }
1178
+ async acquire(bucketKey, signal, path) {
1179
+ await this.#waitGlobal(signal, path);
1180
+ if (!this.#store.reserve) {
1181
+ const bucket = await this.#store.getBucket(bucketKey);
1182
+ if (bucket) {
1183
+ const delay = bucket.resetAt - Date.now();
1184
+ if (bucket.remaining <= 0 && delay > 0)
1185
+ await sleep(delay, signal, path);
1186
+ }
1187
+ return;
1188
+ }
1189
+ for (let attempt = 0;attempt < 8; attempt++) {
1190
+ const reservation = await this.#store.reserve(bucketKey);
1191
+ if (reservation.granted)
1192
+ return;
1193
+ await sleep(Math.max(1, reservation.retryAfterMs), signal, path);
1194
+ await this.#waitGlobal(signal, path);
1195
+ }
1196
+ }
1197
+ async#waitGlobal(signal, path) {
1198
+ const globalDelay = await this.#store.getGlobalReset() - Date.now();
1199
+ if (globalDelay > 0)
1200
+ await sleep(globalDelay, signal, path);
1201
+ }
1202
+ async noteGlobalReset(resetAt) {
1203
+ const current = await this.#store.getGlobalReset();
1204
+ await this.#store.setGlobalReset(Math.max(current, resetAt));
1205
+ }
1206
+ async applyResponse(response, bucketKey, route, major) {
1207
+ let currentKey = bucketKey;
1208
+ const serverBucket = response.headers.get("X-RateLimit-Bucket");
1209
+ if (serverBucket) {
1210
+ await this.#store.setBucketHash(route, serverBucket);
1211
+ currentKey = scopeBucket(serverBucket, major);
1212
+ }
1213
+ const stored = await this.#store.getBucket(currentKey);
1214
+ const bucket = stored ? { ...stored } : { remaining: 1, resetAt: 0 };
1215
+ const windowActive = this.#concurrent && !!stored && stored.resetAt > Date.now();
1216
+ const remaining = Number(response.headers.get("X-RateLimit-Remaining"));
1217
+ const resetAfter = Number(response.headers.get("X-RateLimit-Reset-After"));
1218
+ const reset = Number(response.headers.get("X-RateLimit-Reset"));
1219
+ if (Number.isFinite(remaining))
1220
+ bucket.remaining = Math.max(0, remaining);
1221
+ if (Number.isFinite(resetAfter))
1222
+ bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;
1223
+ else if (Number.isFinite(reset))
1224
+ bucket.resetAt = reset * 1000;
1225
+ if (windowActive && stored) {
1226
+ bucket.remaining = Math.min(bucket.remaining, stored.remaining);
1227
+ bucket.resetAt = Math.max(bucket.resetAt, stored.resetAt);
1228
+ }
1229
+ if (response.status === 429) {
1230
+ const retryAfter = Number(response.headers.get("Retry-After"));
1231
+ if (Number.isFinite(retryAfter))
1232
+ bucket.resetAt = Math.max(bucket.resetAt, Date.now() + retryAfter * 1000);
1233
+ }
1234
+ await this.#store.updateBucket(currentKey, bucket);
1235
+ if (response.headers.get("X-RateLimit-Global") === "true") {
1236
+ const retryAfter = Number(response.headers.get("Retry-After"));
1237
+ if (Number.isFinite(retryAfter))
1238
+ await this.noteGlobalReset(Date.now() + retryAfter * 1000);
1239
+ }
1240
+ return currentKey;
1241
+ }
1242
+ }
1243
+
1244
+ // packages/rest/src/transport.ts
1245
+ class TransportError extends Error {
1246
+ timedOut;
1247
+ constructor(message, timedOut, cause) {
1248
+ super(message, cause === undefined ? undefined : { cause });
1249
+ this.name = "TransportError";
1250
+ this.timedOut = timedOut;
1251
+ }
1252
+ }
1253
+
1254
+ class HttpTransport {
1255
+ #baseURL;
1256
+ #timeout;
1257
+ #fetch;
1258
+ constructor(options = {}) {
1259
+ this.#baseURL = (options.baseURL ?? "https://discord.com/api/v10").replace(/\/$/, "");
1260
+ this.#timeout = Math.max(1, options.timeout ?? 15000);
1261
+ this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
1262
+ }
1263
+ get baseURL() {
1264
+ return this.#baseURL;
1265
+ }
1266
+ get timeout() {
1267
+ return this.#timeout;
1268
+ }
1269
+ async send(request) {
1270
+ if (request.signal?.aborted)
1271
+ throw abortError(request.path, request.signal.reason);
1272
+ const controller = new AbortController;
1273
+ const onAbort = () => controller.abort(request.signal?.reason);
1274
+ request.signal?.addEventListener("abort", onAbort, { once: true });
1275
+ const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
1276
+ try {
1277
+ return await this.#fetch(`${this.#baseURL}${request.path}`, {
1278
+ method: request.method,
1279
+ headers: request.headers,
1280
+ body: request.body,
1281
+ signal: controller.signal
1282
+ });
1283
+ } catch (error) {
1284
+ if (error instanceof RESTError)
1285
+ throw error;
1286
+ const timedOut = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
1287
+ throw new TransportError(timedOut ? `Discord REST request timed out after ${this.#timeout}ms` : "Discord REST request failed", timedOut, error);
1288
+ } finally {
1289
+ clearTimeout(timer);
1290
+ request.signal?.removeEventListener("abort", onAbort);
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ // packages/rest/src/decoder.ts
1296
+ class ResponseDecoder {
1297
+ async read(response) {
1298
+ if (response.status === 204)
1299
+ return;
1300
+ const contentType = response.headers.get("content-type") ?? "";
1301
+ if (contentType.includes("application/json"))
1302
+ return response.json().catch(() => {
1303
+ return;
1304
+ });
1305
+ return response.text().catch(() => {
1306
+ return;
1307
+ });
1308
+ }
1309
+ errorData(payload) {
1310
+ if (!payload || typeof payload !== "object")
1311
+ return {};
1312
+ const data = payload;
1313
+ return {
1314
+ message: typeof data.message === "string" ? data.message : undefined,
1315
+ code: typeof data.code === "number" ? data.code : undefined,
1316
+ retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
1317
+ global: data.global === true
1318
+ };
1319
+ }
1320
+ retryAfter(response, data) {
1321
+ const raw = response.headers.get("Retry-After");
1322
+ const header = raw === null ? Number.NaN : Number(raw);
1323
+ const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
1324
+ return Math.max(0, retryAfter);
1325
+ }
1326
+ }
1327
+
978
1328
  // packages/rest/src/routes.ts
979
1329
  var Routes = {
980
1330
  user: () => "/users/@me",
@@ -1016,6 +1366,8 @@ var Routes = {
1016
1366
  threadMembers: (threadId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members`,
1017
1367
  guildScheduledEvents: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events`,
1018
1368
  guildScheduledEvent: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}`,
1369
+ guildScheduledEventUsers: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}/users`,
1370
+ channelPermission: (channelId, overwriteId) => `/channels/${snowflake(channelId, "Channel ID")}/permissions/${snowflake(overwriteId, "Overwrite ID")}`,
1019
1371
  guildAutoModerationRules: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules`,
1020
1372
  guildAutoModerationRule: (guildId, ruleId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules/${snowflake(ruleId, "Rule ID")}`,
1021
1373
  voiceRegions: () => "/voice/regions",
@@ -1040,22 +1392,6 @@ function snowflake(value, field) {
1040
1392
  }
1041
1393
 
1042
1394
  // packages/rest/src/index.ts
1043
- class RESTError extends Error {
1044
- status;
1045
- code;
1046
- errors;
1047
- method;
1048
- path;
1049
- constructor(message, status, code, errors, options = {}) {
1050
- super(message, options.cause === undefined ? undefined : { cause: options.cause });
1051
- this.name = "RESTError";
1052
- this.status = status;
1053
- this.code = code;
1054
- this.errors = errors;
1055
- this.method = options.method;
1056
- this.path = options.path;
1057
- }
1058
- }
1059
1395
  var LIBRARY_VERSION = package_default.version;
1060
1396
  var LIBRARY_URL = "https://github.com/Ekretos/lunibee";
1061
1397
  var USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${package_default.version})`;
@@ -1082,26 +1418,6 @@ function isRequestData(value) {
1082
1418
  return false;
1083
1419
  return keys.every((key) => REQUEST_DATA_KEYS.has(key));
1084
1420
  }
1085
- function serializeQuery(query) {
1086
- if (query === undefined)
1087
- return "";
1088
- if (typeof query === "string")
1089
- return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
1090
- const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
1091
- const serialized = params.toString();
1092
- return serialized.length === 0 ? "" : `?${serialized}`;
1093
- }
1094
- function majorParameter(path) {
1095
- const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/.exec(path);
1096
- if (!match)
1097
- return "@none";
1098
- if (match[1] === "webhooks" && match[3] !== undefined)
1099
- return `${match[2]}:${match[3]}`;
1100
- return match[2];
1101
- }
1102
- function scopeBucket(hashOrRoute, major) {
1103
- return `${hashOrRoute}|${major}`;
1104
- }
1105
1421
  function createRetryPolicy(maxRetries = 2) {
1106
1422
  if (!Number.isFinite(maxRetries) || maxRetries < 0)
1107
1423
  throw new TypeError("maxRetries must be a non-negative finite number.");
@@ -1109,7 +1425,10 @@ function createRetryPolicy(maxRetries = 2) {
1109
1425
  maxRetries: Math.floor(maxRetries),
1110
1426
  shouldRetry(method, status) {
1111
1427
  const normalized = method.toUpperCase();
1112
- return status === 429 || status >= 500 && status <= 599 && ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
1428
+ const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
1429
+ if (status === 0)
1430
+ return idempotent;
1431
+ return status === 429 || status >= 500 && status <= 599 && idempotent;
1113
1432
  },
1114
1433
  getDelay(attempt, retryAfter) {
1115
1434
  if (retryAfter !== undefined)
@@ -1120,22 +1439,32 @@ function createRetryPolicy(maxRetries = 2) {
1120
1439
  }
1121
1440
 
1122
1441
  class REST {
1123
- #baseURL;
1124
- #timeout;
1125
1442
  #retryPolicy;
1126
1443
  #token;
1127
- #store;
1128
- #localQueues = new Map;
1444
+ #limiter;
1445
+ #scheduler = new RequestScheduler;
1446
+ #transport;
1447
+ #decoder = new ResponseDecoder;
1129
1448
  #hooks = {};
1449
+ #concurrentBuckets;
1130
1450
  constructor(options = {}) {
1131
1451
  this.#token = options.token;
1132
- this.#timeout = Math.max(1, options.timeout ?? 15000);
1133
1452
  this.#retryPolicy = options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);
1134
- this.#baseURL = (options.baseURL ?? "https://discord.com/api/v10").replace(/\/$/, "");
1135
- this.#store = options.store ?? new MemoryRateLimitStore;
1453
+ this.#transport = options.transport ?? new HttpTransport({
1454
+ baseURL: options.baseURL,
1455
+ timeout: options.timeout
1456
+ });
1457
+ const store = options.store ?? new MemoryRateLimitStore;
1458
+ this.#concurrentBuckets = (options.concurrentBuckets ?? false) && typeof store.reserve === "function";
1459
+ this.#limiter = new RateLimiter(store, {
1460
+ concurrent: this.#concurrentBuckets
1461
+ });
1136
1462
  if (options.hooks)
1137
1463
  this.#hooks = options.hooks;
1138
1464
  }
1465
+ get store() {
1466
+ return this.#limiter.store;
1467
+ }
1139
1468
  setHooks(hooks) {
1140
1469
  this.#hooks = hooks;
1141
1470
  return this;
@@ -1146,121 +1475,109 @@ class REST {
1146
1475
  this.#token = token;
1147
1476
  }
1148
1477
  async request(method, path, body, options = {}) {
1149
- const normalizedMethod = method.toUpperCase();
1150
- if (!normalizedMethod)
1478
+ if (!method.toUpperCase())
1151
1479
  throw new TypeError("REST method is required.");
1152
1480
  if (!path.startsWith("/"))
1153
1481
  throw new TypeError("REST paths must start with '/'.");
1482
+ const route = createRouteKey(method, path);
1154
1483
  const files = options.files;
1155
1484
  if (files && files.length > 0 && !(body instanceof FormData))
1156
1485
  body = this.#fileForm(body, files);
1157
1486
  const query = serializeQuery(options.query);
1158
- const finalPath = query ? `${path}${query}` : path;
1159
- const route = `${normalizedMethod}:${this.#normalizeRoute(path)}`;
1160
- const major = majorParameter(path);
1161
- let bucketKey = scopeBucket(await this.#store.getBucketHash(route) ?? route, major);
1162
- const queueKey = bucketKey;
1163
- const previous = this.#localQueues.get(queueKey) ?? Promise.resolve();
1164
- let release;
1165
- const gate = new Promise((resolve) => {
1166
- release = resolve;
1167
- });
1168
- this.#localQueues.set(queueKey, gate);
1169
- await this.#abortable(previous, options.signal, path);
1170
- try {
1171
- for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
1172
- await this.#wait(bucketKey, options.signal, path);
1173
- await this.#waitGlobal(options.signal, path);
1174
- const controller = new AbortController;
1175
- const onAbort = () => controller.abort(options.signal?.reason);
1176
- if (options.signal?.aborted)
1177
- throw this.#abortError(path, options.signal.reason);
1178
- options.signal?.addEventListener("abort", onAbort, {
1179
- once: true
1487
+ const requestPath = query ? `${path}${query}` : path;
1488
+ return this.#scheduler.runResolved(() => this.#limiter.resolveBucketKey(route.route, route.major), options.signal, path, (bucketKey, release) => this.#attempts(route, requestPath, body, options, bucketKey, release));
1489
+ }
1490
+ async#attempts(route, requestPath, body, options, initialBucketKey, release) {
1491
+ const { method, path } = route;
1492
+ let bucketKey = initialBucketKey;
1493
+ for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
1494
+ await this.#limiter.acquire(bucketKey, options.signal, path);
1495
+ if (this.#concurrentBuckets) {
1496
+ const bucket = await this.#limiter.store.getBucket(bucketKey);
1497
+ if (bucket && bucket.resetAt > Date.now())
1498
+ release();
1499
+ }
1500
+ this.#emit(this.#hooks.onRequest, { method, path, attempt });
1501
+ const requestStart = Date.now();
1502
+ let response;
1503
+ try {
1504
+ response = await this.#transport.send({
1505
+ method,
1506
+ path: requestPath,
1507
+ headers: this.#headers(body, options),
1508
+ body: this.#encodeBody(body),
1509
+ signal: options.signal
1180
1510
  });
1181
- const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
1182
- const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
1183
- const headers = { ...options.headers };
1184
- if (options.auth !== false && this.#token)
1185
- headers["Authorization"] = `Bot ${this.#token}`;
1186
- headers["User-Agent"] = USER_AGENT;
1187
- if (options.reason !== undefined)
1188
- headers["X-Audit-Log-Reason"] = encodeURIComponent(options.reason);
1189
- if (!isFormData)
1190
- headers["Content-Type"] = "application/json";
1191
- const fetchBody = isFormData ? body : body === undefined ? undefined : JSON.stringify(body);
1192
- try {
1193
- this.#emit(this.#hooks.onRequest, {
1194
- method: normalizedMethod,
1195
- path,
1196
- attempt
1197
- });
1198
- const requestStart = Date.now();
1199
- const response = await fetch(`${this.#baseURL}${finalPath}`, {
1200
- method: normalizedMethod,
1201
- headers,
1202
- body: fetchBody,
1203
- signal: controller.signal
1204
- });
1205
- this.#emit(this.#hooks.onResponse, {
1206
- method: normalizedMethod,
1207
- path,
1208
- status: response.status,
1209
- durationMs: Date.now() - requestStart
1210
- });
1211
- bucketKey = await this.#update(response, bucketKey, route, major);
1212
- const payload = await this.#readPayload(response);
1213
- if (response.ok)
1214
- return response.status === 204 ? undefined : payload;
1215
- const data = this.#errorData(payload);
1216
- const retryAfter = response.status === 429 ? this.#retryAfter(response, data) : undefined;
1217
- if (data.global && retryAfter !== undefined) {
1218
- const currentGlobal = await this.#store.getGlobalReset();
1219
- await this.#store.setGlobalReset(Math.max(currentGlobal, Date.now() + retryAfter * 1000));
1220
- }
1221
- if (response.status === 429)
1222
- this.#emit(this.#hooks.onRateLimit, {
1223
- method: normalizedMethod,
1224
- path,
1225
- retryAfterMs: (retryAfter ?? 1) * 1000,
1226
- global: data.global ?? false,
1227
- bucket: await this.#store.getBucketHash(route)
1228
- });
1229
- if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, response.status)) {
1230
- const delay = this.#retryPolicy.getDelay(attempt, retryAfter);
1231
- this.#emit(this.#hooks.onRetry, {
1232
- method: normalizedMethod,
1233
- path,
1234
- attempt,
1235
- status: response.status,
1236
- delayMs: delay
1237
- });
1238
- await this.#sleep(delay, options.signal, path);
1239
- continue;
1240
- }
1241
- throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method: normalizedMethod, path });
1242
- } catch (error) {
1243
- if (error instanceof RESTError)
1244
- throw error;
1245
- if (options.signal?.aborted)
1246
- throw this.#abortError(path, options.signal.reason);
1247
- const isTimeout = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
1248
- if (!isTimeout && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, 0)) {
1249
- await this.#sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
1250
- continue;
1251
- }
1252
- 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 });
1253
- } finally {
1254
- clearTimeout(timer);
1255
- options.signal?.removeEventListener("abort", onAbort);
1511
+ } catch (error) {
1512
+ if (error instanceof RESTError)
1513
+ throw error;
1514
+ if (options.signal?.aborted)
1515
+ throw abortError(path, options.signal.reason);
1516
+ const failure = error instanceof TransportError ? error : new TransportError("Discord REST request failed", false, error);
1517
+ if (!failure.timedOut && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, 0)) {
1518
+ await sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
1519
+ continue;
1256
1520
  }
1521
+ throw new RESTError(failure.message, 0, undefined, undefined, {
1522
+ method,
1523
+ path,
1524
+ cause: failure.cause ?? failure
1525
+ });
1257
1526
  }
1258
- throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method: normalizedMethod, path });
1259
- } finally {
1260
- release();
1261
- if (this.#localQueues.get(queueKey) === gate)
1262
- this.#localQueues.delete(queueKey);
1527
+ this.#emit(this.#hooks.onResponse, {
1528
+ method,
1529
+ path,
1530
+ status: response.status,
1531
+ durationMs: Date.now() - requestStart
1532
+ });
1533
+ bucketKey = await this.#limiter.applyResponse(response, bucketKey, route.route, route.major);
1534
+ const payload = await this.#decoder.read(response);
1535
+ if (response.ok)
1536
+ return response.status === 204 ? undefined : payload;
1537
+ const data = this.#decoder.errorData(payload);
1538
+ const retryAfter = response.status === 429 ? this.#decoder.retryAfter(response, data) : undefined;
1539
+ if (data.global && retryAfter !== undefined)
1540
+ await this.#limiter.noteGlobalReset(Date.now() + retryAfter * 1000);
1541
+ if (response.status === 429)
1542
+ this.#emit(this.#hooks.onRateLimit, {
1543
+ method,
1544
+ path,
1545
+ retryAfterMs: (retryAfter ?? 1) * 1000,
1546
+ global: data.global ?? false,
1547
+ bucket: await this.#limiter.store.getBucketHash(route.route)
1548
+ });
1549
+ if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, response.status)) {
1550
+ const delay = this.#retryPolicy.getDelay(attempt, retryAfter);
1551
+ this.#emit(this.#hooks.onRetry, {
1552
+ method,
1553
+ path,
1554
+ attempt,
1555
+ status: response.status,
1556
+ delayMs: delay
1557
+ });
1558
+ await sleep(delay, options.signal, path);
1559
+ continue;
1560
+ }
1561
+ throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method, path });
1263
1562
  }
1563
+ throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method, path });
1564
+ }
1565
+ #headers(body, options) {
1566
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
1567
+ const headers = { ...options.headers };
1568
+ if (options.auth !== false && this.#token)
1569
+ headers["Authorization"] = `Bot ${this.#token}`;
1570
+ headers["User-Agent"] = USER_AGENT;
1571
+ if (options.reason !== undefined)
1572
+ headers["X-Audit-Log-Reason"] = encodeURIComponent(options.reason);
1573
+ if (!isFormData)
1574
+ headers["Content-Type"] = "application/json";
1575
+ return headers;
1576
+ }
1577
+ #encodeBody(body) {
1578
+ if (typeof FormData !== "undefined" && body instanceof FormData)
1579
+ return body;
1580
+ return body === undefined ? undefined : JSON.stringify(body);
1264
1581
  }
1265
1582
  #dispatch(method, path, bodyOrOptions, options) {
1266
1583
  if (options === undefined && isRequestData(bodyOrOptions))
@@ -1305,130 +1622,21 @@ class REST {
1305
1622
  return;
1306
1623
  Promise.resolve().then(() => hook(ctx)).catch(() => {});
1307
1624
  }
1308
- async#wait(bucketKey, signal, path) {
1309
- const bucket = await this.#store.getBucket(bucketKey);
1310
- if (!bucket)
1311
- return;
1312
- const delay = bucket.resetAt - Date.now();
1313
- if (bucket.remaining <= 0 && delay > 0)
1314
- await this.#sleep(delay, signal, path);
1625
+ }
1626
+
1627
+ // packages/structures/src/base.ts
1628
+ class BaseStructure {
1629
+ id;
1630
+ constructor(id) {
1631
+ if (!/^\d{1,20}$/.test(id))
1632
+ throw new TypeError("A Discord structure requires a valid snowflake ID.");
1633
+ this.id = id;
1315
1634
  }
1316
- async#waitGlobal(signal, path) {
1317
- const globalResetAt = await this.#store.getGlobalReset();
1318
- const delay = globalResetAt - Date.now();
1319
- if (delay > 0)
1320
- await this.#sleep(delay, signal, path);
1635
+ toString() {
1636
+ return this.id;
1321
1637
  }
1322
- async#sleep(delay, signal, path) {
1323
- if (delay <= 0)
1324
- return;
1325
- await new Promise((resolve, reject) => {
1326
- const timer = setTimeout(resolve, delay);
1327
- const abort = () => {
1328
- clearTimeout(timer);
1329
- reject(this.#abortError(path, signal?.reason));
1330
- };
1331
- if (signal?.aborted)
1332
- return abort();
1333
- signal?.addEventListener("abort", abort, { once: true });
1334
- });
1335
- }
1336
- async#abortable(promise, signal, path) {
1337
- if (!signal)
1338
- return promise;
1339
- if (signal.aborted)
1340
- throw this.#abortError(path, signal.reason);
1341
- return Promise.race([
1342
- promise,
1343
- new Promise((_, reject) => signal.addEventListener("abort", () => reject(this.#abortError(path, signal.reason)), { once: true }))
1344
- ]);
1345
- }
1346
- #abortError(path, reason) {
1347
- return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
1348
- }
1349
- async#readPayload(response) {
1350
- if (response.status === 204)
1351
- return;
1352
- const contentType = response.headers.get("content-type") ?? "";
1353
- if (contentType.includes("application/json"))
1354
- return response.json().catch(() => {
1355
- return;
1356
- });
1357
- return response.text().catch(() => {
1358
- return;
1359
- });
1360
- }
1361
- #errorData(payload) {
1362
- if (!payload || typeof payload !== "object")
1363
- return {};
1364
- const data = payload;
1365
- return {
1366
- message: typeof data.message === "string" ? data.message : undefined,
1367
- code: typeof data.code === "number" ? data.code : undefined,
1368
- retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
1369
- global: data.global === true
1370
- };
1371
- }
1372
- #retryAfter(response, data) {
1373
- const header = Number(response.headers.get("Retry-After"));
1374
- const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
1375
- return Math.max(0, retryAfter);
1376
- }
1377
- #normalizeRoute(path) {
1378
- const queryIndex = path.indexOf("?");
1379
- const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
1380
- return routePath.replace(/\/\d+(?=\/|$)/g, "/:id");
1381
- }
1382
- async#update(response, bucketKey, route, major) {
1383
- let currentKey = bucketKey;
1384
- const serverBucket = response.headers.get("X-RateLimit-Bucket");
1385
- if (serverBucket) {
1386
- await this.#store.setBucketHash(route, serverBucket);
1387
- currentKey = scopeBucket(serverBucket, major);
1388
- }
1389
- let bucket = await this.#store.getBucket(currentKey) ?? {
1390
- remaining: 1,
1391
- resetAt: 0
1392
- };
1393
- const remaining = Number(response.headers.get("X-RateLimit-Remaining"));
1394
- const resetAfter = Number(response.headers.get("X-RateLimit-Reset-After"));
1395
- const reset = Number(response.headers.get("X-RateLimit-Reset"));
1396
- if (Number.isFinite(remaining))
1397
- bucket.remaining = Math.max(0, remaining);
1398
- if (Number.isFinite(resetAfter))
1399
- bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;
1400
- else if (Number.isFinite(reset))
1401
- bucket.resetAt = reset * 1000;
1402
- if (response.status === 429) {
1403
- const retryAfter = Number(response.headers.get("Retry-After"));
1404
- if (Number.isFinite(retryAfter))
1405
- bucket.resetAt = Math.max(bucket.resetAt, Date.now() + retryAfter * 1000);
1406
- }
1407
- await this.#store.updateBucket(currentKey, bucket);
1408
- if (response.headers.get("X-RateLimit-Global") === "true") {
1409
- const retryAfter = Number(response.headers.get("Retry-After"));
1410
- if (Number.isFinite(retryAfter)) {
1411
- const currentGlobal = await this.#store.getGlobalReset();
1412
- await this.#store.setGlobalReset(Math.max(currentGlobal, Date.now() + retryAfter * 1000));
1413
- }
1414
- }
1415
- return currentKey;
1416
- }
1417
- }
1418
-
1419
- // packages/structures/src/base.ts
1420
- class BaseStructure {
1421
- id;
1422
- constructor(id) {
1423
- if (!/^\d{1,20}$/.test(id))
1424
- throw new TypeError("A Discord structure requires a valid snowflake ID.");
1425
- this.id = id;
1426
- }
1427
- toString() {
1428
- return this.id;
1429
- }
1430
- get createdAt() {
1431
- return new Date(Number((BigInt(this.id) >> 22n) + 1420070400000n));
1638
+ get createdAt() {
1639
+ return new Date(Number((BigInt(this.id) >> 22n) + 1420070400000n));
1432
1640
  }
1433
1641
  }
1434
1642
  var CDN_BASE = "https://cdn.discordapp.com";
@@ -1535,6 +1743,18 @@ class Channel extends BaseStructure {
1535
1743
  update(options) {
1536
1744
  return this.edit(options);
1537
1745
  }
1746
+ isThread() {
1747
+ return this.type === 10 || this.type === 11 || this.type === 12;
1748
+ }
1749
+ isTextBased() {
1750
+ return [0, 1, 2, 3, 5, 10, 11, 12, 13].includes(this.type);
1751
+ }
1752
+ isVoiceBased() {
1753
+ return this.type === 2 || this.type === 13;
1754
+ }
1755
+ isDMBased() {
1756
+ return this.type === 1 || this.type === 3;
1757
+ }
1538
1758
  toString() {
1539
1759
  return `<#${this.id}>`;
1540
1760
  }
@@ -1647,6 +1867,14 @@ class Guild extends BaseStructure {
1647
1867
  }
1648
1868
  }
1649
1869
  // packages/structures/src/resources.ts
1870
+ class TextChannel extends Channel {
1871
+ constructor(data, context) {
1872
+ super(data, context);
1873
+ }
1874
+ toString() {
1875
+ return `<#${this.id}>`;
1876
+ }
1877
+ }
1650
1878
  class AutoModerationRule extends BaseStructure {
1651
1879
  guildId;
1652
1880
  name;
@@ -1689,14 +1917,107 @@ class AutoModerationRule extends BaseStructure {
1689
1917
  };
1690
1918
  }
1691
1919
  }
1692
- // packages/structures/src/interactions.ts
1693
- var InteractionType = {
1694
- Ping: 1,
1695
- ApplicationCommand: 2,
1696
- MessageComponent: 3,
1697
- ApplicationCommandAutocomplete: 4,
1698
- ModalSubmit: 5
1699
- };
1920
+ // packages/structures/src/channels.ts
1921
+ class NewsChannel extends TextChannel {
1922
+ }
1923
+
1924
+ class DMChannel extends Channel {
1925
+ recipients;
1926
+ ownerId;
1927
+ constructor(data, context) {
1928
+ super(data, context);
1929
+ this.recipients = (data.recipients ?? []).map((u) => new User(u));
1930
+ this.ownerId = data.owner_id ?? null;
1931
+ }
1932
+ }
1933
+
1934
+ class VoiceChannel extends Channel {
1935
+ bitrate;
1936
+ userLimit;
1937
+ rtcRegion;
1938
+ videoQualityMode;
1939
+ constructor(data, context) {
1940
+ super(data, context);
1941
+ this.bitrate = data.bitrate ?? 64000;
1942
+ this.userLimit = data.user_limit ?? 0;
1943
+ this.rtcRegion = data.rtc_region ?? null;
1944
+ this.videoQualityMode = data.video_quality_mode ?? 1;
1945
+ }
1946
+ }
1947
+
1948
+ class StageChannel extends VoiceChannel {
1949
+ }
1950
+
1951
+ class CategoryChannel extends Channel {
1952
+ }
1953
+
1954
+ class ThreadChannel extends Channel {
1955
+ ownerId;
1956
+ messageCount;
1957
+ memberCount;
1958
+ metadata;
1959
+ constructor(data, context) {
1960
+ super(data, context);
1961
+ this.ownerId = data.owner_id ?? null;
1962
+ this.messageCount = data.message_count ?? 0;
1963
+ this.memberCount = data.member_count ?? 0;
1964
+ this.metadata = data.thread_metadata ?? null;
1965
+ }
1966
+ get archived() {
1967
+ return this.metadata?.archived ?? false;
1968
+ }
1969
+ get locked() {
1970
+ return this.metadata?.locked ?? false;
1971
+ }
1972
+ get autoArchiveDuration() {
1973
+ return this.metadata?.auto_archive_duration ?? null;
1974
+ }
1975
+ }
1976
+
1977
+ class ForumChannel extends Channel {
1978
+ availableTags;
1979
+ defaultReactionEmoji;
1980
+ defaultThreadRateLimitPerUser;
1981
+ defaultAutoArchiveDuration;
1982
+ constructor(data, context) {
1983
+ super(data, context);
1984
+ this.availableTags = data.available_tags ?? [];
1985
+ this.defaultReactionEmoji = data.default_reaction_emoji ?? null;
1986
+ this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user ?? 0;
1987
+ this.defaultAutoArchiveDuration = data.default_auto_archive_duration ?? null;
1988
+ }
1989
+ }
1990
+
1991
+ class MediaChannel extends ForumChannel {
1992
+ }
1993
+ function createChannel(data, context) {
1994
+ switch (data.type) {
1995
+ case 0:
1996
+ return new TextChannel(data, context);
1997
+ case 1:
1998
+ case 3:
1999
+ return new DMChannel(data, context);
2000
+ case 2:
2001
+ return new VoiceChannel(data, context);
2002
+ case 4:
2003
+ return new CategoryChannel(data, context);
2004
+ case 5:
2005
+ return new NewsChannel(data, context);
2006
+ case 10:
2007
+ case 11:
2008
+ case 12:
2009
+ return new ThreadChannel(data, context);
2010
+ case 13:
2011
+ return new StageChannel(data, context);
2012
+ case 15:
2013
+ return new ForumChannel(data, context);
2014
+ case 16:
2015
+ return new MediaChannel(data, context);
2016
+ default:
2017
+ return new Channel(data, context);
2018
+ }
2019
+ }
2020
+ // packages/structures/src/options.ts
1700
2021
  var OptionType = {
1701
2022
  SubCommand: 1,
1702
2023
  SubCommandGroup: 2,
@@ -1752,6 +2073,12 @@ class CommandOptions {
1752
2073
  }
1753
2074
  return this.#subcommandGroup;
1754
2075
  }
2076
+ getFocused(full = false) {
2077
+ const focused = this.#options.find((o) => o.focused);
2078
+ if (!focused)
2079
+ throw new TypeError("No focused option was provided.");
2080
+ return full ? focused : focused.value ?? "";
2081
+ }
1755
2082
  getString(name, required = false) {
1756
2083
  const opt = this.#get(name);
1757
2084
  if (!opt || opt.type !== OptionType.String) {
@@ -1857,6 +2184,14 @@ class CommandOptions {
1857
2184
  return this.#options;
1858
2185
  }
1859
2186
  }
2187
+ // packages/structures/src/interactions.ts
2188
+ var InteractionType = {
2189
+ Ping: 1,
2190
+ ApplicationCommand: 2,
2191
+ MessageComponent: 3,
2192
+ ApplicationCommandAutocomplete: 4,
2193
+ ModalSubmit: 5
2194
+ };
1860
2195
 
1861
2196
  class InteractionResponse {
1862
2197
  type;
@@ -1920,6 +2255,30 @@ class Interaction {
1920
2255
  isAutocomplete() {
1921
2256
  return this.type === InteractionType.ApplicationCommandAutocomplete;
1922
2257
  }
2258
+ #componentType() {
2259
+ return this.isMessageComponent() ? this.data.data?.component_type ?? 0 : 0;
2260
+ }
2261
+ isButton() {
2262
+ return this.#componentType() === 2;
2263
+ }
2264
+ isStringSelectMenu() {
2265
+ return this.#componentType() === 3;
2266
+ }
2267
+ isUserSelectMenu() {
2268
+ return this.#componentType() === 5;
2269
+ }
2270
+ isRoleSelectMenu() {
2271
+ return this.#componentType() === 6;
2272
+ }
2273
+ isMentionableSelectMenu() {
2274
+ return this.#componentType() === 7;
2275
+ }
2276
+ isChannelSelectMenu() {
2277
+ return this.#componentType() === 8;
2278
+ }
2279
+ isAnySelectMenu() {
2280
+ return [3, 5, 6, 7, 8].includes(this.#componentType());
2281
+ }
1923
2282
  assertUnacknowledged() {
1924
2283
  if (this.replied || this.deferred)
1925
2284
  throw new Error("Interaction has already been acknowledged.");
@@ -2030,6 +2389,11 @@ class ModalSubmitInteraction extends Interaction {
2030
2389
  }
2031
2390
 
2032
2391
  class AutocompleteInteraction extends Interaction {
2392
+ options;
2393
+ constructor(client, data) {
2394
+ super(client, data);
2395
+ this.options = new CommandOptions(data.data?.options ?? []);
2396
+ }
2033
2397
  get commandName() {
2034
2398
  return this.data?.data?.name ?? "";
2035
2399
  }
@@ -2287,7 +2651,7 @@ class ThreadManager {
2287
2651
  auto_archive_duration: options.autoArchiveDuration,
2288
2652
  rate_limit_per_user: options.rateLimitPerUser
2289
2653
  });
2290
- return new Channel(data, this.#context);
2654
+ return createChannel(data, this.#context);
2291
2655
  }
2292
2656
  }
2293
2657
 
@@ -2315,18 +2679,173 @@ class UserManager extends ResourceManager {
2315
2679
  });
2316
2680
  }
2317
2681
  }
2318
- // packages/managers/src/guild.ts
2682
+ // packages/managers/src/guild-resources.ts
2319
2683
  function withQuery(path, params) {
2684
+ const query = new URLSearchParams;
2685
+ for (const [key, value] of Object.entries(params))
2686
+ if (value !== undefined)
2687
+ query.set(key, String(value));
2688
+ const suffix = query.toString();
2689
+ return suffix ? `${path}?${suffix}` : path;
2690
+ }
2691
+
2692
+ class GuildBanManager extends Manager {
2693
+ #rest;
2694
+ guildId;
2695
+ constructor(rest, guildId) {
2696
+ super();
2697
+ this.#rest = rest;
2698
+ this.guildId = guildId;
2699
+ }
2700
+ async fetch(userId) {
2701
+ const ban = await this.#rest.get(Routes.guildBan(this.guildId, userId));
2702
+ this.set(ban.user.id, ban);
2703
+ return ban;
2704
+ }
2705
+ async fetchAll(options = {}) {
2706
+ const limit = options.limit === undefined ? undefined : Math.min(1000, Math.max(1, options.limit));
2707
+ const bans = await this.#rest.get(withQuery(Routes.guildBans(this.guildId), {
2708
+ limit,
2709
+ before: options.before,
2710
+ after: options.after
2711
+ }));
2712
+ for (const ban of bans)
2713
+ this.set(ban.user.id, ban);
2714
+ return bans;
2715
+ }
2716
+ async create(userId, options = {}) {
2717
+ await this.#rest.put(Routes.guildBan(this.guildId, userId), { delete_message_seconds: options.deleteMessageSeconds }, { reason: options.reason });
2718
+ }
2719
+ async remove(userId, reason) {
2720
+ await this.#rest.delete(Routes.guildBan(this.guildId, userId), {
2721
+ reason
2722
+ });
2723
+ this.delete(userId);
2724
+ }
2725
+ }
2726
+
2727
+ class GuildScheduledEventManager extends Manager {
2728
+ #rest;
2729
+ guildId;
2730
+ constructor(rest, guildId) {
2731
+ super();
2732
+ this.#rest = rest;
2733
+ this.guildId = guildId;
2734
+ }
2735
+ #store(event) {
2736
+ this.set(event.id, event);
2737
+ return event;
2738
+ }
2739
+ async fetch(eventId, options = {}) {
2740
+ return this.#store(await this.#rest.get(withQuery(Routes.guildScheduledEvent(this.guildId, eventId), {
2741
+ with_user_count: options.withUserCount
2742
+ })));
2743
+ }
2744
+ async fetchAll(options = {}) {
2745
+ const events = await this.#rest.get(withQuery(Routes.guildScheduledEvents(this.guildId), {
2746
+ with_user_count: options.withUserCount
2747
+ }));
2748
+ return events.map((event) => this.#store(event));
2749
+ }
2750
+ async create(options, reason) {
2751
+ return this.#store(await this.#rest.post(Routes.guildScheduledEvents(this.guildId), options, { reason }));
2752
+ }
2753
+ async edit(eventId, options, reason) {
2754
+ return this.#store(await this.#rest.patch(Routes.guildScheduledEvent(this.guildId, eventId), options, { reason }));
2755
+ }
2756
+ async remove(eventId) {
2757
+ await this.#rest.delete(Routes.guildScheduledEvent(this.guildId, eventId));
2758
+ this.delete(eventId);
2759
+ }
2760
+ async fetchSubscribers(eventId, options = {}) {
2761
+ const limit = options.limit === undefined ? undefined : Math.min(100, Math.max(1, options.limit));
2762
+ return this.#rest.get(withQuery(Routes.guildScheduledEventUsers(this.guildId, eventId), {
2763
+ limit,
2764
+ with_member: options.withMember,
2765
+ before: options.before,
2766
+ after: options.after
2767
+ }));
2768
+ }
2769
+ }
2770
+
2771
+ class StageInstanceManager extends Manager {
2772
+ #rest;
2773
+ constructor(rest) {
2774
+ super();
2775
+ this.#rest = rest;
2776
+ }
2777
+ #store(stage) {
2778
+ this.set(stage.channel_id, stage);
2779
+ return stage;
2780
+ }
2781
+ async create(channelId, options, reason) {
2782
+ return this.#store(await this.#rest.post(Routes.stageInstances(), {
2783
+ channel_id: channelId,
2784
+ topic: options.topic,
2785
+ privacy_level: options.privacyLevel,
2786
+ send_start_notification: options.sendStartNotification,
2787
+ guild_scheduled_event_id: options.guildScheduledEventId
2788
+ }, { reason }));
2789
+ }
2790
+ async fetch(channelId) {
2791
+ return this.#store(await this.#rest.get(Routes.stageInstanceByChannel(channelId)));
2792
+ }
2793
+ async edit(channelId, options, reason) {
2794
+ return this.#store(await this.#rest.patch(Routes.stageInstanceByChannel(channelId), { topic: options.topic, privacy_level: options.privacyLevel }, { reason }));
2795
+ }
2796
+ async remove(channelId, reason) {
2797
+ await this.#rest.delete(Routes.stageInstanceByChannel(channelId), {
2798
+ reason
2799
+ });
2800
+ this.delete(channelId);
2801
+ }
2802
+ }
2803
+
2804
+ class PermissionOverwriteManager {
2805
+ #rest;
2806
+ channelId;
2807
+ constructor(rest, channelId) {
2808
+ this.#rest = rest;
2809
+ this.channelId = channelId;
2810
+ }
2811
+ async edit(overwriteId, options, reason) {
2812
+ await this.#rest.put(Routes.channelPermission(this.channelId, overwriteId), {
2813
+ type: options.type,
2814
+ allow: BigInt(options.allow ?? 0).toString(),
2815
+ deny: BigInt(options.deny ?? 0).toString()
2816
+ }, { reason });
2817
+ }
2818
+ async remove(overwriteId, reason) {
2819
+ await this.#rest.delete(Routes.channelPermission(this.channelId, overwriteId), { reason });
2820
+ }
2821
+ }
2822
+
2823
+ // packages/managers/src/guild.ts
2824
+ function withQuery2(path, params) {
2320
2825
  const suffix = params.toString();
2321
2826
  return suffix ? `${path}?${suffix}` : path;
2322
2827
  }
2323
2828
 
2324
2829
  class GuildManager extends ResourceManager {
2325
2830
  #rest;
2831
+ #bans = new Map;
2832
+ #scheduledEvents = new Map;
2326
2833
  constructor(rest) {
2327
2834
  super(async (id) => new Guild(await rest.get(Routes.guild(id))), (guild) => guild.id);
2328
2835
  this.#rest = rest;
2329
2836
  }
2837
+ bans(guildId) {
2838
+ let manager = this.#bans.get(guildId);
2839
+ if (!manager)
2840
+ this.#bans.set(guildId, manager = new GuildBanManager(this.#rest, guildId));
2841
+ return manager;
2842
+ }
2843
+ scheduledEvents(guildId) {
2844
+ let manager = this.#scheduledEvents.get(guildId);
2845
+ if (!manager)
2846
+ this.#scheduledEvents.set(guildId, manager = new GuildScheduledEventManager(this.#rest, guildId));
2847
+ return manager;
2848
+ }
2330
2849
  async create(options) {
2331
2850
  const data = await this.#rest.post("/guilds", options);
2332
2851
  return this.upsert(new Guild(data));
@@ -2362,7 +2881,7 @@ class GuildManager extends ResourceManager {
2362
2881
  params.set("after", options.after);
2363
2882
  if (options.limit !== undefined)
2364
2883
  params.set("limit", String(Math.min(100, Math.max(1, options.limit))));
2365
- return this.#rest.get(withQuery(Routes.guildAuditLog(guildId), params));
2884
+ return this.#rest.get(withQuery2(Routes.guildAuditLog(guildId), params));
2366
2885
  }
2367
2886
  async fetchMembers(guildId, options = {}) {
2368
2887
  const params = new URLSearchParams;
@@ -2370,7 +2889,7 @@ class GuildManager extends ResourceManager {
2370
2889
  params.set("limit", String(Math.min(1000, Math.max(1, options.limit))));
2371
2890
  if (options.after)
2372
2891
  params.set("after", options.after);
2373
- return this.#rest.get(withQuery(Routes.guildMembers(guildId), params));
2892
+ return this.#rest.get(withQuery2(Routes.guildMembers(guildId), params));
2374
2893
  }
2375
2894
  async fetchAutoModerationRules(guildId) {
2376
2895
  const rules = await this.#rest.get(Routes.guildAutoModerationRules(guildId));
@@ -2463,6 +2982,9 @@ class ChannelManager extends Manager {
2463
2982
  }
2464
2983
  return manager;
2465
2984
  }
2985
+ permissionOverwrites(channelId) {
2986
+ return new PermissionOverwriteManager(this.#rest, channelId);
2987
+ }
2466
2988
  threads(channelId) {
2467
2989
  return new ThreadManager(this.#rest, this.#context, channelId);
2468
2990
  }
@@ -2474,7 +2996,7 @@ class ChannelManager extends Manager {
2474
2996
  }
2475
2997
  upsert(data) {
2476
2998
  const existing = this.get(data.id);
2477
- const channel = new Channel(data, this.#context);
2999
+ const channel = createChannel(data, this.#context);
2478
3000
  if (existing) {
2479
3001
  Object.assign(existing, channel);
2480
3002
  return existing;
@@ -2610,20 +3132,203 @@ function isMessageQuery(value) {
2610
3132
  return typeof value === "object" && value !== null && !(Symbol.iterator in value);
2611
3133
  }
2612
3134
 
2613
- // packages/ws/src/index.ts
2614
- var GatewayOpcodes = {
2615
- Dispatch: 0,
2616
- Heartbeat: 1,
2617
- Identify: 2,
2618
- PresenceUpdate: 3,
2619
- VoiceStateUpdate: 4,
2620
- Resume: 6,
2621
- Reconnect: 7,
2622
- RequestGuildMembers: 8,
2623
- InvalidSession: 9,
2624
- Hello: 10,
2625
- HeartbeatAck: 11
2626
- };
3135
+ // packages/ws/src/session.ts
3136
+ class GatewaySession {
3137
+ #state = { status: "none" };
3138
+ #sequence = null;
3139
+ #generation = 0;
3140
+ #lastInvalidation;
3141
+ get lastInvalidation() {
3142
+ return this.#lastInvalidation;
3143
+ }
3144
+ get generation() {
3145
+ return this.#generation;
3146
+ }
3147
+ get sessionId() {
3148
+ return this.#state.status === "established" ? this.#state.sessionId : undefined;
3149
+ }
3150
+ get resumeURL() {
3151
+ return this.#state.status === "established" ? this.#state.resumeURL : undefined;
3152
+ }
3153
+ get sequence() {
3154
+ return this.#sequence;
3155
+ }
3156
+ get canResume() {
3157
+ return this.#state.status === "established" && this.#sequence !== null;
3158
+ }
3159
+ beginConnection() {
3160
+ return ++this.#generation;
3161
+ }
3162
+ owns(token) {
3163
+ return token === this.#generation;
3164
+ }
3165
+ recordSequence(sequence, token) {
3166
+ if (!this.owns(token))
3167
+ return false;
3168
+ if (!Number.isInteger(sequence))
3169
+ return false;
3170
+ this.#sequence = sequence;
3171
+ return true;
3172
+ }
3173
+ activate(payload, token) {
3174
+ if (!this.owns(token))
3175
+ return false;
3176
+ const ready = payload;
3177
+ if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string")
3178
+ return false;
3179
+ this.#state = {
3180
+ status: "established",
3181
+ sessionId: ready.session_id,
3182
+ resumeURL: ready.resume_gateway_url
3183
+ };
3184
+ return true;
3185
+ }
3186
+ invalidate(token, reason = "explicit") {
3187
+ if (!this.owns(token))
3188
+ return false;
3189
+ this.#lastInvalidation = reason;
3190
+ this.#state = { status: "none" };
3191
+ this.#sequence = null;
3192
+ return true;
3193
+ }
3194
+ resumeInfo() {
3195
+ if (this.#state.status !== "established")
3196
+ return;
3197
+ if (this.#sequence === null)
3198
+ return;
3199
+ const { sessionId, resumeURL } = this.#state;
3200
+ return { sessionId, sequence: this.#sequence, resumeURL };
3201
+ }
3202
+ handshake() {
3203
+ const resume = this.resumeInfo();
3204
+ return resume ? { type: "resume", ...resume } : { type: "identify" };
3205
+ }
3206
+ connectURL(fallback) {
3207
+ return this.resumeInfo()?.resumeURL ?? fallback;
3208
+ }
3209
+ }
3210
+
3211
+ // packages/ws/src/heartbeat.ts
3212
+ var MAX_MONITOR_INTERVAL = 1000;
3213
+
3214
+ class GatewayHeartbeat {
3215
+ #options;
3216
+ #now;
3217
+ #interval = 0;
3218
+ #acknowledged = true;
3219
+ #sentAt = 0;
3220
+ #latency = -1;
3221
+ #lastMessageAt = 0;
3222
+ #zombieReported = false;
3223
+ #timer;
3224
+ #initialTimer;
3225
+ #ackTimer;
3226
+ #monitorTimer;
3227
+ constructor(options) {
3228
+ if (options.zombieTimeout <= options.ackTimeout)
3229
+ throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
3230
+ this.#options = options;
3231
+ this.#now = options.now ?? (() => Date.now());
3232
+ }
3233
+ get latency() {
3234
+ return this.#latency;
3235
+ }
3236
+ get interval() {
3237
+ return this.#interval;
3238
+ }
3239
+ get acknowledged() {
3240
+ return this.#acknowledged;
3241
+ }
3242
+ get silentFor() {
3243
+ return this.#lastMessageAt === 0 ? 0 : this.#now() - this.#lastMessageAt;
3244
+ }
3245
+ get stalenessDeadline() {
3246
+ return Math.max(this.#options.zombieTimeout, this.#interval + this.#options.ackTimeout);
3247
+ }
3248
+ isHealthy() {
3249
+ if (!this.#acknowledged)
3250
+ return false;
3251
+ if (this.#lastMessageAt === 0)
3252
+ return true;
3253
+ return this.silentFor < this.stalenessDeadline;
3254
+ }
3255
+ watch() {
3256
+ this.#lastMessageAt = this.#now();
3257
+ this.#zombieReported = false;
3258
+ this.#startMonitor();
3259
+ }
3260
+ receivedMessage() {
3261
+ this.#lastMessageAt = this.#now();
3262
+ this.#zombieReported = false;
3263
+ }
3264
+ start(interval) {
3265
+ this.#clearBeatTimers();
3266
+ this.#interval = interval;
3267
+ this.#acknowledged = true;
3268
+ this.#initialTimer = setTimeout(() => this.sendHeartbeat(), Math.random() * interval);
3269
+ this.#timer = setInterval(() => this.sendHeartbeat(), interval);
3270
+ }
3271
+ stop() {
3272
+ this.#clearBeatTimers();
3273
+ if (this.#monitorTimer)
3274
+ clearInterval(this.#monitorTimer);
3275
+ this.#monitorTimer = undefined;
3276
+ }
3277
+ sendHeartbeat() {
3278
+ this.#acknowledged = false;
3279
+ this.#sentAt = this.#now();
3280
+ if (!this.#options.send(this.#options.sequence())) {
3281
+ this.#options.onError(new Error("Unable to send Gateway heartbeat because the WebSocket is not open."));
3282
+ return;
3283
+ }
3284
+ this.#clearAckTimer();
3285
+ this.#ackTimer = setTimeout(() => {
3286
+ if (this.#acknowledged)
3287
+ return;
3288
+ this.#options.onTimeout({
3289
+ type: "ack",
3290
+ elapsedMs: this.#now() - this.#sentAt
3291
+ });
3292
+ }, this.#options.ackTimeout);
3293
+ }
3294
+ acknowledge() {
3295
+ this.#acknowledged = true;
3296
+ this.#latency = this.#now() - this.#sentAt;
3297
+ this.#clearAckTimer();
3298
+ }
3299
+ #startMonitor() {
3300
+ if (this.#monitorTimer)
3301
+ clearInterval(this.#monitorTimer);
3302
+ const interval = Math.max(1, Math.min(MAX_MONITOR_INTERVAL, this.#options.ackTimeout, this.#options.zombieTimeout / 2));
3303
+ this.#monitorTimer = setInterval(() => this.#checkStaleness(), interval);
3304
+ }
3305
+ #checkStaleness() {
3306
+ if (!this.#options.isConnected() || this.#lastMessageAt === 0)
3307
+ return;
3308
+ const silentFor = this.silentFor;
3309
+ const deadline = this.stalenessDeadline;
3310
+ if (silentFor < deadline || this.#zombieReported)
3311
+ return;
3312
+ this.#zombieReported = true;
3313
+ this.#options.onTimeout({ type: "zombie", silentFor, deadline });
3314
+ }
3315
+ #clearAckTimer() {
3316
+ if (this.#ackTimer)
3317
+ clearTimeout(this.#ackTimer);
3318
+ this.#ackTimer = undefined;
3319
+ }
3320
+ #clearBeatTimers() {
3321
+ if (this.#timer)
3322
+ clearInterval(this.#timer);
3323
+ if (this.#initialTimer)
3324
+ clearTimeout(this.#initialTimer);
3325
+ this.#clearAckTimer();
3326
+ this.#timer = undefined;
3327
+ this.#initialTimer = undefined;
3328
+ }
3329
+ }
3330
+
3331
+ // packages/ws/src/close-codes.ts
2627
3332
  var GatewayCloseCodes = {
2628
3333
  UnknownError: 4000,
2629
3334
  UnknownOpcode: 4001,
@@ -2640,6 +3345,490 @@ var GatewayCloseCodes = {
2640
3345
  InvalidIntents: 4013,
2641
3346
  DisallowedIntents: 4014
2642
3347
  };
3348
+
3349
+ // packages/ws/src/reconnect.ts
3350
+ var FATAL_CLOSE_CODES = [
3351
+ GatewayCloseCodes.AuthenticationFailed,
3352
+ GatewayCloseCodes.InvalidShard,
3353
+ GatewayCloseCodes.ShardingRequired,
3354
+ GatewayCloseCodes.InvalidAPIVersion,
3355
+ GatewayCloseCodes.InvalidIntents,
3356
+ GatewayCloseCodes.DisallowedIntents
3357
+ ];
3358
+ var IDENTIFY_CLOSE_CODES = [
3359
+ GatewayCloseCodes.InvalidSeq,
3360
+ GatewayCloseCodes.SessionTimedOut
3361
+ ];
3362
+ function classifyCloseCode(code, canResume) {
3363
+ if (FATAL_CLOSE_CODES.includes(code))
3364
+ return "stop";
3365
+ if (IDENTIFY_CLOSE_CODES.includes(code))
3366
+ return "identify";
3367
+ return canResume ? "resume" : "identify";
3368
+ }
3369
+
3370
+ class GatewayReconnect {
3371
+ #options;
3372
+ #random;
3373
+ #attempt = 0;
3374
+ #timer;
3375
+ #promise;
3376
+ #resolve;
3377
+ #reject;
3378
+ constructor(options) {
3379
+ this.#options = options;
3380
+ this.#random = options.random ?? Math.random;
3381
+ }
3382
+ get enabled() {
3383
+ return this.#options.enabled;
3384
+ }
3385
+ get attempts() {
3386
+ return this.#attempt;
3387
+ }
3388
+ get pending() {
3389
+ return this.#timer !== undefined;
3390
+ }
3391
+ get exhausted() {
3392
+ return this.#attempt >= this.#options.maxAttempts;
3393
+ }
3394
+ get connecting() {
3395
+ return this.#promise !== undefined;
3396
+ }
3397
+ get attemptPromise() {
3398
+ return this.#promise;
3399
+ }
3400
+ classifyClose(code, canResume) {
3401
+ return classifyCloseCode(code, canResume);
3402
+ }
3403
+ canReconnect() {
3404
+ return this.#options.enabled && !this.exhausted;
3405
+ }
3406
+ schedule(run) {
3407
+ if (!this.#options.enabled)
3408
+ return { scheduled: false, reason: "disabled" };
3409
+ if (this.#timer)
3410
+ return { scheduled: false, reason: "pending" };
3411
+ if (this.exhausted)
3412
+ return { scheduled: false, reason: "exhausted" };
3413
+ const attempt = this.#attempt;
3414
+ const delay = Math.min(this.#options.maxDelay, this.#options.baseDelay * 2 ** this.#attempt++);
3415
+ const jitter = this.#random() * Math.max(1, delay * 0.25);
3416
+ const delayMs = delay + jitter;
3417
+ this.#timer = setTimeout(() => {
3418
+ this.#timer = undefined;
3419
+ run();
3420
+ }, delayMs);
3421
+ return { scheduled: true, delayMs, attempt };
3422
+ }
3423
+ cancel() {
3424
+ if (this.#timer)
3425
+ clearTimeout(this.#timer);
3426
+ this.#timer = undefined;
3427
+ }
3428
+ reset() {
3429
+ this.#attempt = 0;
3430
+ }
3431
+ attempt(start) {
3432
+ if (this.#promise)
3433
+ return this.#promise;
3434
+ this.#promise = new Promise((resolve, reject) => {
3435
+ this.#resolve = resolve;
3436
+ this.#reject = reject;
3437
+ start();
3438
+ });
3439
+ return this.#promise;
3440
+ }
3441
+ settle(error) {
3442
+ const resolve = this.#resolve;
3443
+ const reject = this.#reject;
3444
+ this.#resolve = undefined;
3445
+ this.#reject = undefined;
3446
+ this.#promise = undefined;
3447
+ if (error)
3448
+ reject?.(error);
3449
+ else
3450
+ resolve?.();
3451
+ }
3452
+ }
3453
+
3454
+ // packages/ws/src/decoder.ts
3455
+ import {
3456
+ createInflate,
3457
+ constants as zlibConstants
3458
+ } from "zlib";
3459
+ async function toBytes(data) {
3460
+ if (data instanceof Uint8Array)
3461
+ return data;
3462
+ if (data instanceof ArrayBuffer)
3463
+ return new Uint8Array(data);
3464
+ if (typeof Blob !== "undefined" && data instanceof Blob)
3465
+ return new Uint8Array(await data.arrayBuffer());
3466
+ throw new TypeError("Gateway returned an unsupported compressed frame type.");
3467
+ }
3468
+
3469
+ class PlainTextDecoder {
3470
+ push(data) {
3471
+ return [String(data)];
3472
+ }
3473
+ reset() {}
3474
+ }
3475
+
3476
+ class ZlibStreamDecoder {
3477
+ #create;
3478
+ #inflate;
3479
+ #chunks = [];
3480
+ #failure;
3481
+ #pendingReject;
3482
+ constructor(create = () => createInflate()) {
3483
+ this.#create = create;
3484
+ }
3485
+ push(data) {
3486
+ if (typeof data === "string")
3487
+ return [data];
3488
+ return this.#inflateFrame(data);
3489
+ }
3490
+ async#inflateFrame(data) {
3491
+ const chunk = await toBytes(data);
3492
+ const inflate = this.#inflate ??= this.#open();
3493
+ if (this.#failure)
3494
+ throw this.#failure;
3495
+ 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;
3496
+ await this.#awaiting((settle) => inflate.write(chunk, (error) => error ? settle(error) : settle()));
3497
+ if (!complete)
3498
+ return [];
3499
+ await this.#awaiting((settle) => inflate.flush(zlibConstants.Z_SYNC_FLUSH, () => settle()));
3500
+ const parts = this.#chunks;
3501
+ this.#chunks = [];
3502
+ if (parts.length === 0)
3503
+ return [];
3504
+ const total = parts.reduce((size, part) => size + part.length, 0);
3505
+ const merged = new Uint8Array(total);
3506
+ let offset = 0;
3507
+ for (const part of parts) {
3508
+ merged.set(part, offset);
3509
+ offset += part.length;
3510
+ }
3511
+ return [new TextDecoder().decode(merged)];
3512
+ }
3513
+ reset() {
3514
+ const inflate = this.#inflate;
3515
+ this.#inflate = undefined;
3516
+ this.#chunks = [];
3517
+ this.#failure = undefined;
3518
+ this.#pendingReject = undefined;
3519
+ if (!inflate)
3520
+ return;
3521
+ inflate.removeAllListeners();
3522
+ try {
3523
+ inflate.close();
3524
+ } catch {}
3525
+ }
3526
+ #awaiting(run) {
3527
+ if (this.#failure)
3528
+ return Promise.reject(this.#failure);
3529
+ return new Promise((resolve, reject) => {
3530
+ this.#pendingReject = reject;
3531
+ run((error) => {
3532
+ this.#pendingReject = undefined;
3533
+ if (error)
3534
+ reject(error);
3535
+ else if (this.#failure)
3536
+ reject(this.#failure);
3537
+ else
3538
+ resolve();
3539
+ });
3540
+ });
3541
+ }
3542
+ #open() {
3543
+ const inflate = this.#create();
3544
+ inflate.on("data", (chunk) => this.#chunks.push(chunk));
3545
+ inflate.on("error", (error) => {
3546
+ this.#failure = error;
3547
+ const reject = this.#pendingReject;
3548
+ this.#pendingReject = undefined;
3549
+ reject?.(error);
3550
+ });
3551
+ return inflate;
3552
+ }
3553
+ }
3554
+ function createDecoder(compress) {
3555
+ return compress ? new ZlibStreamDecoder : new PlainTextDecoder;
3556
+ }
3557
+
3558
+ // packages/ws/src/transport.ts
3559
+ var OPEN = 1;
3560
+
3561
+ class WebSocketTransport {
3562
+ #handlers;
3563
+ #decoder;
3564
+ #createSocket;
3565
+ #compress;
3566
+ #socket;
3567
+ #generation = 0;
3568
+ #queue = Promise.resolve();
3569
+ constructor(options) {
3570
+ this.#handlers = options.handlers;
3571
+ this.#compress = options.compress ?? false;
3572
+ this.#decoder = options.decoder ?? createDecoder(options.compress);
3573
+ this.#createSocket = options.createSocket ?? ((url) => new WebSocket(url));
3574
+ }
3575
+ get connected() {
3576
+ return this.#socket?.readyState === OPEN;
3577
+ }
3578
+ get live() {
3579
+ return this.#socket !== undefined && this.#socket.readyState <= OPEN;
3580
+ }
3581
+ get generation() {
3582
+ return this.#generation;
3583
+ }
3584
+ connect(url) {
3585
+ this.#discard(1000, "Superseded by a new connection");
3586
+ const generation = ++this.#generation;
3587
+ this.#decoder.reset();
3588
+ this.#queue = Promise.resolve();
3589
+ let socket;
3590
+ try {
3591
+ socket = this.#createSocket(url);
3592
+ } catch (error) {
3593
+ return { ok: false, error: this.#asError(error) };
3594
+ }
3595
+ if (this.#compress) {
3596
+ try {
3597
+ socket.binaryType = "arraybuffer";
3598
+ } catch {}
3599
+ }
3600
+ this.#socket = socket;
3601
+ socket.addEventListener("open", () => {
3602
+ if (!this.#owns(generation))
3603
+ return;
3604
+ this.#handlers.onOpen();
3605
+ });
3606
+ socket.addEventListener("message", (event) => {
3607
+ if (!this.#owns(generation))
3608
+ return;
3609
+ let decoded;
3610
+ try {
3611
+ decoded = this.#decoder.push(event.data);
3612
+ } catch (error) {
3613
+ this.#handlers.onError(this.#asError(error));
3614
+ return;
3615
+ }
3616
+ if (!(decoded instanceof Promise)) {
3617
+ for (const frame of decoded)
3618
+ this.#handlers.onFrame(frame);
3619
+ return;
3620
+ }
3621
+ this.#queue = this.#queue.then(async () => {
3622
+ const frames = await decoded;
3623
+ if (!this.#owns(generation))
3624
+ return;
3625
+ for (const frame of frames)
3626
+ this.#handlers.onFrame(frame);
3627
+ }).catch((error) => {
3628
+ if (!this.#owns(generation))
3629
+ return;
3630
+ this.#handlers.onError(this.#asError(error));
3631
+ });
3632
+ });
3633
+ socket.addEventListener("close", (event) => {
3634
+ if (!this.#owns(generation))
3635
+ return;
3636
+ this.#socket = undefined;
3637
+ this.#generation++;
3638
+ this.#decoder.reset();
3639
+ this.#handlers.onClose(event.code, event.reason ?? "");
3640
+ });
3641
+ socket.addEventListener("error", () => {
3642
+ if (!this.#owns(generation))
3643
+ return;
3644
+ this.#handlers.onError(new Error("Gateway WebSocket error"));
3645
+ });
3646
+ return { ok: true };
3647
+ }
3648
+ send(data) {
3649
+ if (!this.connected)
3650
+ return false;
3651
+ try {
3652
+ this.#socket.send(data);
3653
+ return true;
3654
+ } catch (error) {
3655
+ this.#handlers.onError(this.#asError(error));
3656
+ return false;
3657
+ }
3658
+ }
3659
+ close(code = 1000, reason = "") {
3660
+ const socket = this.#socket;
3661
+ if (!socket)
3662
+ return;
3663
+ try {
3664
+ socket.close(code, reason);
3665
+ } catch (error) {
3666
+ this.#handlers.onError(this.#asError(error));
3667
+ }
3668
+ }
3669
+ destroy(code = 1000, reason = "") {
3670
+ this.#discard(code, reason);
3671
+ this.#decoder.reset();
3672
+ }
3673
+ #owns(generation) {
3674
+ return generation === this.#generation;
3675
+ }
3676
+ #discard(code, reason) {
3677
+ const socket = this.#socket;
3678
+ if (!socket)
3679
+ return;
3680
+ this.#generation++;
3681
+ this.#socket = undefined;
3682
+ try {
3683
+ socket.close(code, reason);
3684
+ } catch (error) {
3685
+ this.#handlers.onError(this.#asError(error));
3686
+ }
3687
+ }
3688
+ #asError(error) {
3689
+ return error instanceof Error ? error : new Error(String(error));
3690
+ }
3691
+ }
3692
+
3693
+ // packages/ws/src/opcodes.ts
3694
+ var GatewayOpcodes = {
3695
+ Dispatch: 0,
3696
+ Heartbeat: 1,
3697
+ Identify: 2,
3698
+ PresenceUpdate: 3,
3699
+ VoiceStateUpdate: 4,
3700
+ Resume: 6,
3701
+ Reconnect: 7,
3702
+ RequestGuildMembers: 8,
3703
+ InvalidSession: 9,
3704
+ Hello: 10,
3705
+ HeartbeatAck: 11
3706
+ };
3707
+
3708
+ // packages/ws/src/protocol.ts
3709
+ function classifyFrame(raw) {
3710
+ let payload;
3711
+ try {
3712
+ payload = JSON.parse(raw);
3713
+ } catch (error) {
3714
+ return {
3715
+ sequence: null,
3716
+ action: {
3717
+ type: "invalid",
3718
+ violation: "invalid-json",
3719
+ message: "Gateway returned invalid JSON",
3720
+ cause: error
3721
+ }
3722
+ };
3723
+ }
3724
+ return classifyPayload(payload);
3725
+ }
3726
+ function classifyPayload(payload) {
3727
+ if (payload === null || typeof payload !== "object" || typeof payload.op !== "number")
3728
+ return {
3729
+ sequence: null,
3730
+ action: {
3731
+ type: "invalid",
3732
+ violation: "invalid-payload",
3733
+ message: "Gateway returned an invalid payload"
3734
+ }
3735
+ };
3736
+ const frame = payload;
3737
+ const sequence = typeof frame.s === "number" ? frame.s : null;
3738
+ switch (frame.op) {
3739
+ case GatewayOpcodes.Dispatch:
3740
+ return {
3741
+ sequence,
3742
+ action: {
3743
+ type: "dispatch",
3744
+ event: typeof frame.t === "string" ? frame.t : null,
3745
+ data: frame.d
3746
+ }
3747
+ };
3748
+ case GatewayOpcodes.Hello: {
3749
+ const interval = frame.d?.heartbeat_interval;
3750
+ if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0)
3751
+ return {
3752
+ sequence,
3753
+ action: {
3754
+ type: "invalid",
3755
+ violation: "invalid-hello",
3756
+ message: "Gateway HELLO payload contains an invalid heartbeat interval"
3757
+ }
3758
+ };
3759
+ return {
3760
+ sequence,
3761
+ action: { type: "hello", heartbeatInterval: interval }
3762
+ };
3763
+ }
3764
+ case GatewayOpcodes.Heartbeat:
3765
+ return { sequence, action: { type: "heartbeat" } };
3766
+ case GatewayOpcodes.HeartbeatAck:
3767
+ return {
3768
+ sequence,
3769
+ action: { type: "heartbeat-ack", data: frame.d }
3770
+ };
3771
+ case GatewayOpcodes.Reconnect:
3772
+ return { sequence, action: { type: "reconnect" } };
3773
+ case GatewayOpcodes.InvalidSession:
3774
+ return {
3775
+ sequence,
3776
+ action: {
3777
+ type: "invalid-session",
3778
+ resumable: frame.d === true
3779
+ }
3780
+ };
3781
+ default:
3782
+ return { sequence, action: { type: "unknown", opcode: frame.op } };
3783
+ }
3784
+ }
3785
+ function identifyPayload(options) {
3786
+ const props = options.properties ?? {};
3787
+ const os = props.os ?? "Android";
3788
+ const browser = props.browser ?? "Discord Android";
3789
+ const device = props.device ?? "Discord Android";
3790
+ return {
3791
+ op: GatewayOpcodes.Identify,
3792
+ d: {
3793
+ token: options.token,
3794
+ intents: options.intents,
3795
+ properties: {
3796
+ os,
3797
+ browser,
3798
+ device,
3799
+ $os: os,
3800
+ $browser: browser,
3801
+ $device: device,
3802
+ ...props
3803
+ },
3804
+ presence: {
3805
+ since: options.presence?.since ?? null,
3806
+ activities: options.presence?.activities ?? [],
3807
+ status: options.presence?.status ?? "online",
3808
+ afk: Boolean(options.presence?.afk)
3809
+ },
3810
+ shard: [options.shardId, options.shardCount]
3811
+ },
3812
+ s: null,
3813
+ t: null
3814
+ };
3815
+ }
3816
+ function resumePayload(token, resume) {
3817
+ return {
3818
+ op: GatewayOpcodes.Resume,
3819
+ d: {
3820
+ token,
3821
+ session_id: resume.sessionId,
3822
+ seq: resume.sequence
3823
+ },
3824
+ s: null,
3825
+ t: null
3826
+ };
3827
+ }
3828
+ function heartbeatPayload(sequence) {
3829
+ return { op: GatewayOpcodes.Heartbeat, d: sequence, s: null, t: null };
3830
+ }
3831
+ // packages/ws/src/state.ts
2643
3832
  class GatewayError extends Error {
2644
3833
  code;
2645
3834
  constructor(message, code, options) {
@@ -2648,33 +3837,43 @@ class GatewayError extends Error {
2648
3837
  this.code = code;
2649
3838
  }
2650
3839
  }
3840
+ // packages/ws/src/send-budget.ts
3841
+ class SendBudget {
3842
+ #timestamps = [];
3843
+ #limit;
3844
+ #windowMs;
3845
+ constructor(limit = 115, windowMs = 60000) {
3846
+ this.#limit = limit;
3847
+ this.#windowMs = windowMs;
3848
+ }
3849
+ get used() {
3850
+ return this.#timestamps.length;
3851
+ }
3852
+ allows(privileged, now = Date.now()) {
3853
+ while (this.#timestamps.length && now - this.#timestamps[0] >= this.#windowMs)
3854
+ this.#timestamps.shift();
3855
+ return privileged || this.#timestamps.length < this.#limit;
3856
+ }
3857
+ record(now = Date.now()) {
3858
+ this.#timestamps.push(now);
3859
+ }
3860
+ }
3861
+
3862
+ // packages/ws/src/index.ts
3863
+ var DEFAULT_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
2651
3864
 
2652
3865
  class Gateway {
2653
3866
  state = "CONNECT" /* Connect */;
2654
3867
  #options;
2655
- #ws;
2656
- #sequence = null;
2657
- #sessionId;
2658
- #resumeURL;
2659
- #heartbeatTimer;
2660
- #initialHeartbeat;
2661
- #heartbeatAckTimer;
2662
- #heartbeatACK = true;
2663
- #heartbeatInterval = 0;
2664
- #heartbeatSentAt = 0;
2665
- #lastMessageAt = 0;
2666
- #zombieTimer;
2667
- #zombieReported = false;
3868
+ #transport;
3869
+ #session = new GatewaySession;
3870
+ #token = 0;
3871
+ #heartbeat;
2668
3872
  #closed = false;
2669
- #attempt = 0;
2670
- #reconnectTimer;
2671
- #connectPromise;
2672
- #resolveConnect;
2673
- #rejectConnect;
3873
+ #reconnect;
2674
3874
  #listeners = new Map;
2675
- #sendTimestamps = [];
3875
+ #sendBudget = new SendBudget;
2676
3876
  ping = -1;
2677
- #decompressor;
2678
3877
  constructor(options) {
2679
3878
  if (!options.token?.trim())
2680
3879
  throw new TypeError("A Gateway token is required.");
@@ -2701,41 +3900,72 @@ class Gateway {
2701
3900
  afk: false,
2702
3901
  since: null
2703
3902
  },
2704
- ...options
3903
+ ...options,
3904
+ intents: resolvedIntents
2705
3905
  };
2706
3906
  if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
2707
3907
  throw new RangeError("Gateway shardId must be within the configured shard count.");
2708
3908
  if (this.#options.zombieTimeout <= this.#options.heartbeatAckTimeout)
2709
3909
  throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
3910
+ this.#transport = new WebSocketTransport({
3911
+ compress: this.#options.compress,
3912
+ createSocket: options.createSocket,
3913
+ handlers: {
3914
+ onOpen: () => this.#onOpen(),
3915
+ onFrame: (raw) => this.#message(raw, this.#token),
3916
+ onClose: (code) => this.#close(code, this.#token),
3917
+ onError: (error) => this.#emitError(error instanceof GatewayError ? error : new GatewayError(error.message, undefined, {
3918
+ cause: error
3919
+ }))
3920
+ }
3921
+ });
3922
+ this.#reconnect = new GatewayReconnect({
3923
+ enabled: this.#options.reconnect,
3924
+ maxAttempts: this.#options.maxReconnectAttempts,
3925
+ baseDelay: this.#options.reconnectBaseDelay,
3926
+ maxDelay: this.#options.reconnectMaxDelay
3927
+ });
3928
+ this.#heartbeat = new GatewayHeartbeat({
3929
+ ackTimeout: this.#options.heartbeatAckTimeout,
3930
+ zombieTimeout: this.#options.zombieTimeout,
3931
+ send: (sequence) => this.#dispatch(heartbeatPayload(sequence), true),
3932
+ sequence: () => this.#session.sequence,
3933
+ isConnected: () => !this.#closed && this.#transport.connected,
3934
+ onTimeout: (timeout) => this.#onHeartbeatTimeout(timeout),
3935
+ onError: (error) => this.#emitError(error)
3936
+ });
3937
+ }
3938
+ #onHeartbeatTimeout(timeout) {
3939
+ if (timeout.type === "zombie") {
3940
+ this.#emit("zombie", {
3941
+ silentFor: timeout.silentFor,
3942
+ timeout: timeout.deadline
3943
+ });
3944
+ this.#emitError(new GatewayError(`Gateway connection appears stale after ${timeout.silentFor}ms without traffic.`));
3945
+ } else {
3946
+ this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${timeout.elapsedMs}ms.`));
3947
+ }
3948
+ this.#transport.close(1001, timeout.type === "zombie" ? "Zombie Gateway connection" : "Heartbeat timeout");
2710
3949
  }
2711
- connect(url = "wss://gateway.discord.gg/?v=10&encoding=json") {
3950
+ connect(url = DEFAULT_GATEWAY_URL) {
2712
3951
  if (this.state === "CLOSED" /* Closed */)
2713
3952
  throw new GatewayError("Gateway has been permanently closed.");
2714
- if (this.#connectPromise)
2715
- return this.#connectPromise;
3953
+ const inFlight = this.#reconnect.attemptPromise;
3954
+ if (inFlight)
3955
+ return inFlight;
3956
+ if (this.#transport.live)
3957
+ return Promise.resolve();
3958
+ this.#reconnect.cancel();
2716
3959
  this.#closed = false;
2717
3960
  this.#setState("CONNECT" /* Connect */);
2718
3961
  const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
2719
- if (this.#options.compress)
2720
- this.#initDecompressor();
2721
- this.#connectPromise = new Promise((resolve, reject) => {
2722
- this.#resolveConnect = resolve;
2723
- this.#rejectConnect = reject;
2724
- this.#open(connectURL);
2725
- });
2726
- return this.#connectPromise;
3962
+ return this.#reconnect.attempt(() => this.#open(connectURL));
2727
3963
  }
2728
3964
  close() {
2729
3965
  this.#closed = true;
2730
3966
  this.#clearTimers();
2731
3967
  this.#settleConnect(new GatewayError("Gateway connection closed before socket open."));
2732
- const ws = this.#ws;
2733
- this.#ws = undefined;
2734
- try {
2735
- ws?.close(1000, "Client closed connection");
2736
- } catch (error) {
2737
- this.#emitError(error);
2738
- }
3968
+ this.#transport.destroy(1000, "Client closed connection");
2739
3969
  this.#setState("CLOSED" /* Closed */);
2740
3970
  }
2741
3971
  emit(event, data) {
@@ -2760,23 +3990,17 @@ class Gateway {
2760
3990
  return this.#dispatch(payload, false);
2761
3991
  }
2762
3992
  #dispatch(payload, privileged) {
2763
- if (this.#ws?.readyState !== WebSocket.OPEN)
3993
+ if (!this.#transport.connected)
2764
3994
  return false;
2765
3995
  const now = Date.now();
2766
- while (this.#sendTimestamps.length && now - this.#sendTimestamps[0] >= 60000)
2767
- this.#sendTimestamps.shift();
2768
- if (!privileged && this.#sendTimestamps.length >= 115) {
3996
+ if (!this.#sendBudget.allows(privileged, now)) {
2769
3997
  this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
2770
3998
  return false;
2771
3999
  }
2772
- try {
2773
- this.#ws.send(JSON.stringify(payload));
2774
- this.#sendTimestamps.push(now);
2775
- return true;
2776
- } catch (error) {
2777
- this.#emitError(error);
4000
+ if (!this.#transport.send(JSON.stringify(payload)))
2778
4001
  return false;
2779
- }
4002
+ this.#sendBudget.record(now);
4003
+ return true;
2780
4004
  }
2781
4005
  setPresence(data) {
2782
4006
  this.#options.presence = { ...this.#options.presence, ...data };
@@ -2812,127 +4036,80 @@ class Gateway {
2812
4036
  #open(url) {
2813
4037
  if (this.#closed)
2814
4038
  return;
2815
- let ws;
2816
- try {
2817
- ws = new WebSocket(url);
2818
- } catch (error) {
2819
- const failure = this.#normalizeError(error);
2820
- this.#emitError(failure);
2821
- if (this.#options.reconnect) {
2822
- this.#setState("RECONNECT" /* Reconnect */);
2823
- this.#scheduleReconnect();
2824
- } else
2825
- this.#settleConnect(failure);
4039
+ this.#token = this.#session.beginConnection();
4040
+ const result = this.#transport.connect(url);
4041
+ if (result.ok)
2826
4042
  return;
4043
+ const failure = this.#normalizeError(result.error);
4044
+ this.#emitError(failure);
4045
+ if (this.#options.reconnect) {
4046
+ this.#setState("RECONNECT" /* Reconnect */);
4047
+ this.#scheduleReconnect();
4048
+ } else {
4049
+ this.#settleConnect(failure);
2827
4050
  }
2828
- this.#ws = ws;
2829
- ws.addEventListener("open", () => {
2830
- this.#lastMessageAt = Date.now();
2831
- this.#zombieReported = false;
2832
- this.#startZombieDetection();
2833
- this.#emit("open", undefined);
2834
- this.#settleConnect();
2835
- });
2836
- ws.addEventListener("message", (event) => {
2837
- this.#lastMessageAt = Date.now();
2838
- this.#zombieReported = false;
2839
- if (this.#options.compress && event.data instanceof ArrayBuffer) {
2840
- this.#decompress(new Uint8Array(event.data)).then((text) => this.#message(ws, text)).catch((err) => this.#emitError(err));
2841
- } else {
2842
- this.#message(ws, String(event.data));
2843
- }
2844
- });
2845
- ws.addEventListener("close", (event) => this.#close(ws, event.code));
2846
- ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
2847
- }
2848
- #startZombieDetection() {
2849
- if (this.#zombieTimer)
2850
- clearInterval(this.#zombieTimer);
2851
- const interval = Math.max(1, Math.min(1000, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
2852
- this.#zombieTimer = setInterval(() => {
2853
- if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
2854
- return;
2855
- const silentFor = Date.now() - this.#lastMessageAt;
2856
- const deadline = Math.max(this.#options.zombieTimeout, this.#heartbeatInterval + this.#options.heartbeatAckTimeout);
2857
- if (silentFor < deadline || this.#zombieReported)
2858
- return;
2859
- this.#zombieReported = true;
2860
- const error = new GatewayError(`Gateway connection appears stale after ${silentFor}ms without traffic.`);
2861
- this.#emit("zombie", { silentFor, timeout: deadline });
2862
- this.#emitError(error);
2863
- try {
2864
- this.#ws.close(1001, "Zombie Gateway connection");
2865
- } catch (closeError) {
2866
- this.#emitError(closeError);
2867
- }
2868
- }, interval);
2869
4051
  }
2870
- #message(ws, raw) {
2871
- let payload;
2872
- try {
2873
- payload = JSON.parse(raw);
2874
- } catch (error) {
2875
- this.#emitError(new GatewayError("Gateway returned invalid JSON", undefined, {
2876
- cause: error
2877
- }));
2878
- ws.close(1002, "Invalid JSON");
2879
- return;
2880
- }
2881
- if (!payload || typeof payload.op !== "number") {
2882
- this.#emitError(new GatewayError("Gateway returned an invalid payload"));
2883
- ws.close(1002, "Invalid payload");
4052
+ #onOpen() {
4053
+ this.#heartbeat.watch();
4054
+ this.#emit("open", undefined);
4055
+ this.#settleConnect();
4056
+ }
4057
+ #message(raw, token) {
4058
+ if (!this.#session.owns(token))
2884
4059
  return;
2885
- }
2886
- if (typeof payload.s === "number")
2887
- this.#sequence = payload.s;
2888
- switch (payload.op) {
2889
- case GatewayOpcodes.Dispatch:
4060
+ const { sequence, action } = classifyFrame(raw);
4061
+ if (sequence !== null)
4062
+ this.#session.recordSequence(sequence, token);
4063
+ this.#apply(action, token);
4064
+ }
4065
+ #apply(action, token) {
4066
+ switch (action.type) {
4067
+ case "dispatch":
2890
4068
  this.#setState("DISPATCH" /* Dispatch */);
2891
- this.#handleDispatch(payload.t, payload.d);
2892
- break;
2893
- case GatewayOpcodes.Hello:
2894
- this.#handleHello(payload.d);
2895
- break;
2896
- case GatewayOpcodes.Heartbeat:
4069
+ this.#handleDispatch(action.event, action.data, token);
4070
+ return;
4071
+ case "hello":
4072
+ this.#handleHello(action.heartbeatInterval);
4073
+ return;
4074
+ case "heartbeat":
2897
4075
  this.#setState("HEARTBEAT" /* Heartbeat */);
2898
- this.#sendHeartbeat();
2899
- break;
2900
- case GatewayOpcodes.HeartbeatAck:
2901
- this.#heartbeatACK = true;
2902
- this.ping = Date.now() - this.#heartbeatSentAt;
2903
- this.#clearHeartbeatAckTimer();
2904
- this.#emit("heartbeatAck", payload.d);
2905
- break;
2906
- case GatewayOpcodes.Reconnect:
2907
- ws.close(1001, "Server requested reconnect");
2908
- break;
2909
- case GatewayOpcodes.InvalidSession: {
2910
- const resumable = payload.d === true;
2911
- if (!resumable) {
2912
- this.#sessionId = undefined;
2913
- this.#sequence = null;
2914
- this.#resumeURL = undefined;
2915
- }
2916
- this.#emit("invalidSession", resumable);
2917
- ws.close(resumable ? GatewayCloseCodes.UnknownError : 1000, resumable ? "Invalid session (resumable)" : "Invalid session");
2918
- break;
4076
+ this.#heartbeat.sendHeartbeat();
4077
+ return;
4078
+ case "heartbeat-ack":
4079
+ this.#heartbeat.acknowledge();
4080
+ this.ping = this.#heartbeat.latency;
4081
+ this.#emit("heartbeatAck", action.data);
4082
+ return;
4083
+ case "reconnect":
4084
+ this.#transport.close(1001, "Server requested reconnect");
4085
+ return;
4086
+ case "invalid-session": {
4087
+ if (!action.resumable)
4088
+ this.#session.invalidate(token, "invalid-session");
4089
+ this.#emit("invalidSession", action.resumable);
4090
+ this.#transport.close(action.resumable ? GatewayCloseCodes.UnknownError : 1000, action.resumable ? "Invalid session (resumable)" : "Invalid session");
4091
+ return;
2919
4092
  }
4093
+ case "invalid":
4094
+ this.#emitError(new GatewayError(action.message, undefined, action.cause === undefined ? undefined : { cause: action.cause }));
4095
+ this.#transport.close(1002, action.violation === "invalid-json" ? "Invalid JSON" : action.violation === "invalid-hello" ? "Invalid heartbeat interval" : "Invalid payload");
4096
+ return;
4097
+ case "unknown":
4098
+ return;
2920
4099
  }
2921
4100
  }
2922
- #handleDispatch(event, data) {
4101
+ #handleDispatch(event, data, token) {
2923
4102
  if (event === "READY") {
2924
- const ready = data;
2925
- if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string") {
2926
- this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
4103
+ if (!this.#session.activate(data, token)) {
4104
+ if (this.#session.owns(token))
4105
+ this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
2927
4106
  return;
2928
4107
  }
2929
- this.#sessionId = ready.session_id;
2930
- this.#resumeURL = ready.resume_gateway_url;
2931
- this.#attempt = 0;
4108
+ this.#reconnect.reset();
2932
4109
  this.#setState("READY" /* Ready */);
2933
4110
  this.#emit("ready", data);
2934
4111
  } else if (event === "RESUMED") {
2935
- this.#attempt = 0;
4112
+ this.#reconnect.reset();
2936
4113
  this.#setState("READY" /* Ready */);
2937
4114
  this.#emit("resumed", data);
2938
4115
  }
@@ -2941,183 +4118,60 @@ class Gateway {
2941
4118
  return;
2942
4119
  this.#emit(event ?? "dispatch", data);
2943
4120
  }
2944
- #handleHello(data) {
2945
- const interval = data?.heartbeat_interval;
2946
- if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0) {
2947
- this.#emitError(new GatewayError("Gateway HELLO payload contains an invalid heartbeat interval"));
2948
- this.#ws?.close(1002, "Invalid heartbeat interval");
2949
- return;
2950
- }
2951
- this.#heartbeatInterval = interval;
2952
- this.#setState(this.#sessionId && this.#sequence !== null && this.#resumeURL ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
2953
- this.#startHeartbeat(interval);
4121
+ #handleHello(interval) {
4122
+ this.#setState(this.#session.canResume ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
4123
+ this.#heartbeat.start(interval);
2954
4124
  this.#identifyOrResume();
2955
4125
  }
2956
4126
  #identifyOrResume() {
2957
- if (this.#sessionId && this.#sequence !== null && this.#resumeURL) {
4127
+ const intent = this.#session.handshake();
4128
+ if (intent.type === "resume") {
2958
4129
  this.#setState("RESUME" /* Resume */);
2959
- this.send({
2960
- op: GatewayOpcodes.Resume,
2961
- d: {
2962
- token: this.#options.token,
2963
- session_id: this.#sessionId,
2964
- seq: this.#sequence
2965
- },
2966
- s: null,
2967
- t: null
2968
- });
4130
+ this.#dispatch(resumePayload(this.#options.token, intent), true);
2969
4131
  return;
2970
4132
  }
2971
4133
  this.#setState("IDENTIFY" /* Identify */);
2972
- const props = this.#options.properties ?? {
2973
- os: "Android",
2974
- browser: "Discord Android",
2975
- device: "Discord Android"
2976
- };
2977
- const os = props.os ?? "Android";
2978
- const browser = props.browser ?? "Discord Android";
2979
- const device = props.device ?? "Discord Android";
2980
- this.send({
2981
- op: GatewayOpcodes.Identify,
2982
- d: {
2983
- token: this.#options.token,
2984
- intents: this.#options.intents,
2985
- properties: {
2986
- os,
2987
- browser,
2988
- device,
2989
- $os: os,
2990
- $browser: browser,
2991
- $device: device,
2992
- ...props
2993
- },
2994
- presence: {
2995
- since: this.#options.presence?.since ?? null,
2996
- activities: this.#options.presence?.activities ?? [],
2997
- status: this.#options.presence?.status ?? "online",
2998
- afk: Boolean(this.#options.presence?.afk)
2999
- },
3000
- shard: [this.#options.shardId, this.#options.shardCount]
3001
- },
3002
- s: null,
3003
- t: null
3004
- });
3005
- }
3006
- #startHeartbeat(interval) {
3007
- this.#clearHeartbeatTimers();
3008
- this.#heartbeatACK = true;
3009
- this.#initialHeartbeat = setTimeout(() => this.#sendHeartbeat(), Math.random() * interval);
3010
- this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), interval);
3011
- }
3012
- #sendHeartbeat() {
3013
- this.#heartbeatACK = false;
3014
- this.#heartbeatSentAt = Date.now();
3015
- if (!this.#dispatch({
3016
- op: GatewayOpcodes.Heartbeat,
3017
- d: this.#sequence,
3018
- s: null,
3019
- t: null
3020
- }, true)) {
3021
- this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
3022
- return;
3023
- }
3024
- this.#clearHeartbeatAckTimer();
3025
- this.#heartbeatAckTimer = setTimeout(() => {
3026
- if (!this.#heartbeatACK) {
3027
- const elapsed = Date.now() - this.#heartbeatSentAt;
3028
- this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${elapsed}ms.`));
3029
- try {
3030
- this.#ws?.close(1001, "Heartbeat timeout");
3031
- } catch (error) {
3032
- this.#emitError(error);
3033
- }
3034
- }
3035
- }, this.#options.heartbeatAckTimeout);
3036
- }
3037
- #close(ws, code) {
3038
- if (this.#ws !== ws)
3039
- return;
3040
- this.#ws = undefined;
4134
+ this.#dispatch(identifyPayload({
4135
+ token: this.#options.token,
4136
+ intents: this.#options.intents,
4137
+ shardId: this.#options.shardId,
4138
+ shardCount: this.#options.shardCount,
4139
+ properties: this.#options.properties,
4140
+ presence: this.#options.presence
4141
+ }), true);
4142
+ }
4143
+ #close(code, token) {
3041
4144
  this.#clearTimers();
3042
- const action = this.#closeAction(code);
4145
+ const action = this.#reconnect.classifyClose(code, this.#session.canResume);
3043
4146
  this.#emit("close", { code, action });
3044
- if (this.#closed || !this.#options.reconnect || action === "stop") {
4147
+ if (this.#closed || !this.#reconnect.enabled || action === "stop") {
4148
+ if (action === "stop")
4149
+ this.#closed = true;
3045
4150
  this.#setState(this.#closed ? "CLOSED" /* Closed */ : "CONNECT" /* Connect */);
3046
- if (this.#connectPromise)
3047
- this.#settleConnect(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
4151
+ if (this.#reconnect.connecting)
4152
+ this.#reconnect.settle(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
3048
4153
  return;
3049
4154
  }
3050
- if (action === "identify") {
3051
- this.#sequence = null;
3052
- this.#sessionId = undefined;
3053
- this.#resumeURL = undefined;
3054
- }
4155
+ if (action === "identify")
4156
+ this.#session.invalidate(token, "session-timeout");
3055
4157
  this.#setState("RECONNECT" /* Reconnect */);
3056
4158
  this.#scheduleReconnect();
3057
4159
  }
3058
- #closeAction(code) {
3059
- const fatal = [
3060
- GatewayCloseCodes.AuthenticationFailed,
3061
- GatewayCloseCodes.InvalidShard,
3062
- GatewayCloseCodes.ShardingRequired,
3063
- GatewayCloseCodes.InvalidAPIVersion,
3064
- GatewayCloseCodes.InvalidIntents,
3065
- GatewayCloseCodes.DisallowedIntents
3066
- ];
3067
- if (fatal.includes(code))
3068
- return "stop";
3069
- if (code === GatewayCloseCodes.InvalidSeq || code === GatewayCloseCodes.SessionTimedOut)
3070
- return "identify";
3071
- return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
3072
- }
3073
4160
  #scheduleReconnect() {
3074
- if (this.#closed || !this.#options.reconnect || this.#reconnectTimer)
4161
+ if (this.#closed)
3075
4162
  return;
3076
- if (this.#attempt >= this.#options.maxReconnectAttempts) {
3077
- this.#settleConnect(new GatewayError("Gateway reconnect attempts exhausted."));
3078
- this.#setState("CONNECT" /* Connect */);
4163
+ const result = this.#reconnect.schedule(() => this.#open(this.#session.connectURL(DEFAULT_GATEWAY_URL)));
4164
+ if (result.scheduled || result.reason !== "exhausted")
3079
4165
  return;
3080
- }
3081
- const delay = Math.min(this.#options.reconnectMaxDelay, this.#options.reconnectBaseDelay * 2 ** this.#attempt++);
3082
- const jitter = Math.random() * Math.max(1, delay * 0.25);
3083
- this.#reconnectTimer = setTimeout(() => {
3084
- this.#reconnectTimer = undefined;
3085
- this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
3086
- }, delay + jitter);
4166
+ this.#reconnect.settle(new GatewayError("Gateway reconnect attempts exhausted."));
4167
+ this.#setState("CONNECT" /* Connect */);
3087
4168
  }
3088
4169
  #settleConnect(error) {
3089
- const resolve = this.#resolveConnect;
3090
- const reject = this.#rejectConnect;
3091
- this.#resolveConnect = undefined;
3092
- this.#rejectConnect = undefined;
3093
- this.#connectPromise = undefined;
3094
- if (error)
3095
- reject?.(error);
3096
- else
3097
- resolve?.();
3098
- }
3099
- #clearHeartbeatAckTimer() {
3100
- if (this.#heartbeatAckTimer)
3101
- clearTimeout(this.#heartbeatAckTimer);
3102
- this.#heartbeatAckTimer = undefined;
3103
- }
3104
- #clearHeartbeatTimers() {
3105
- if (this.#heartbeatTimer)
3106
- clearInterval(this.#heartbeatTimer);
3107
- if (this.#initialHeartbeat)
3108
- clearTimeout(this.#initialHeartbeat);
3109
- this.#clearHeartbeatAckTimer();
3110
- this.#heartbeatTimer = undefined;
3111
- this.#initialHeartbeat = undefined;
4170
+ this.#reconnect.settle(error);
3112
4171
  }
3113
4172
  #clearTimers() {
3114
- if (this.#reconnectTimer)
3115
- clearTimeout(this.#reconnectTimer);
3116
- if (this.#zombieTimer)
3117
- clearInterval(this.#zombieTimer);
3118
- this.#reconnectTimer = undefined;
3119
- this.#zombieTimer = undefined;
3120
- this.#clearHeartbeatTimers();
4173
+ this.#reconnect.cancel();
4174
+ this.#heartbeat.stop();
3121
4175
  }
3122
4176
  #normalizeError(error) {
3123
4177
  return error instanceof GatewayError ? error : new GatewayError("Gateway connection failed.", undefined, {
@@ -3149,37 +4203,6 @@ class Gateway {
3149
4203
  this.state = next;
3150
4204
  this.#emit("stateChange", { previous, next });
3151
4205
  }
3152
- #initDecompressor() {
3153
- const ds = new DecompressionStream("deflate-raw");
3154
- this.#decompressor = {
3155
- writer: ds.writable.getWriter(),
3156
- reader: ds.readable.getReader()
3157
- };
3158
- }
3159
- async#decompress(chunk) {
3160
- if (!this.#decompressor)
3161
- this.#initDecompressor();
3162
- const { writer, reader } = this.#decompressor;
3163
- await writer.write(chunk);
3164
- const parts = [];
3165
- while (true) {
3166
- const { done, value } = await Promise.race([
3167
- reader.read(),
3168
- new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), 50))
3169
- ]);
3170
- if (done || !value)
3171
- break;
3172
- parts.push(value);
3173
- }
3174
- const total = parts.reduce((n, p) => n + p.length, 0);
3175
- const merged = new Uint8Array(total);
3176
- let offset = 0;
3177
- for (const p of parts) {
3178
- merged.set(p, offset);
3179
- offset += p.length;
3180
- }
3181
- return new TextDecoder().decode(merged);
3182
- }
3183
4206
  }
3184
4207
 
3185
4208
  // packages/core/src/index.ts
@@ -3250,6 +4273,7 @@ class Client extends EventEmitter2 {
3250
4273
  users;
3251
4274
  guilds;
3252
4275
  channels;
4276
+ stageInstances;
3253
4277
  application;
3254
4278
  get ws() {
3255
4279
  return this.#gateway;
@@ -3292,14 +4316,15 @@ class Client extends EventEmitter2 {
3292
4316
  this.users = new UserManager(this.rest);
3293
4317
  this.guilds = new GuildManager(this.rest);
3294
4318
  this.channels = new ChannelManager(this.rest);
4319
+ this.stageInstances = new StageInstanceManager(this.rest);
3295
4320
  const placeholderAppCommands = new ApplicationCommandManager(this.rest, "0");
3296
4321
  this.application = { commands: placeholderAppCommands };
3297
4322
  this.#resourceContext = {
3298
- sendMessage: (channelId, options2) => this.channels.send(channelId, options2),
3299
- editMessage: (channelId, messageId, options2) => this.channels.editMessage(channelId, messageId, options2),
4323
+ sendMessage: (channelId, options) => this.channels.send(channelId, options),
4324
+ editMessage: (channelId, messageId, options) => this.channels.editMessage(channelId, messageId, options),
3300
4325
  deleteMessage: (channelId, messageId) => this.channels.deleteMessage(channelId, messageId),
3301
4326
  crosspostMessage: (channelId, messageId) => this.channels.crosspostMessage(channelId, messageId),
3302
- editChannel: (channelId, options2) => this.channels.edit(channelId, options2),
4327
+ editChannel: (channelId, options) => this.channels.edit(channelId, options),
3303
4328
  deleteChannel: (channelId) => this.channels.deleteChannel(channelId),
3304
4329
  addReaction: (channelId, messageId, emoji) => this.channels.addReaction(channelId, messageId, emoji),
3305
4330
  removeOwnReaction: (channelId, messageId, emoji) => this.channels.removeOwnReaction(channelId, messageId, emoji),
@@ -3371,7 +4396,7 @@ class Client extends EventEmitter2 {
3371
4396
  ...payload.channels ?? [],
3372
4397
  ...payload.threads ?? []
3373
4398
  ])
3374
- this.channels.set(channelData.id, new Channel(channelData, this.#resourceContext));
4399
+ this.channels.set(channelData.id, createChannel(channelData, this.#resourceContext));
3375
4400
  this.emit(wasCached ? "guildAvailable" /* GuildAvailable */ : "guildCreate" /* GuildCreate */, payload);
3376
4401
  });
3377
4402
  this.#gateway.on("GUILD_UPDATE", (data) => {
@@ -3418,12 +4443,12 @@ class Client extends EventEmitter2 {
3418
4443
  this.#gateway.on("AUTO_MODERATION_RULE_DELETE", (data) => this.emit("autoModerationRuleDelete" /* AutoModerationRuleDelete */, data));
3419
4444
  this.#gateway.on("AUTO_MODERATION_ACTION_EXECUTION", (data) => this.emit("autoModerationActionExecution" /* AutoModerationActionExecution */, data));
3420
4445
  this.#gateway.on("CHANNEL_CREATE", (data) => {
3421
- const channel = new Channel(data, this.#resourceContext);
4446
+ const channel = createChannel(data, this.#resourceContext);
3422
4447
  this.channels.update(channel);
3423
4448
  this.emit("channelCreate" /* ChannelCreate */, channel);
3424
4449
  });
3425
4450
  this.#gateway.on("CHANNEL_UPDATE", (data) => {
3426
- const channel = new Channel(data, this.#resourceContext);
4451
+ const channel = createChannel(data, this.#resourceContext);
3427
4452
  this.channels.update(channel);
3428
4453
  this.emit("channelUpdate" /* ChannelUpdate */, channel);
3429
4454
  });
@@ -3434,12 +4459,12 @@ class Client extends EventEmitter2 {
3434
4459
  });
3435
4460
  this.#gateway.on("CHANNEL_PINS_UPDATE", (data) => this.emit("channelPinsUpdate" /* ChannelPinsUpdate */, data));
3436
4461
  this.#gateway.on("THREAD_CREATE", (data) => {
3437
- const channel = new Channel(data, this.#resourceContext);
4462
+ const channel = createChannel(data, this.#resourceContext);
3438
4463
  this.channels.update(channel);
3439
4464
  this.emit("threadCreate" /* ThreadCreate */, channel);
3440
4465
  });
3441
4466
  this.#gateway.on("THREAD_UPDATE", (data) => {
3442
- const channel = new Channel(data, this.#resourceContext);
4467
+ const channel = createChannel(data, this.#resourceContext);
3443
4468
  this.channels.update(channel);
3444
4469
  this.emit("threadUpdate" /* ThreadUpdate */, channel);
3445
4470
  });
@@ -3519,7 +4544,7 @@ class Client extends EventEmitter2 {
3519
4544
  if (options?.guildScheduledEventId)
3520
4545
  query.set("guild_scheduled_event_id", options.guildScheduledEventId);
3521
4546
  const qs = query.toString();
3522
- return this.rest.get(`/invites/${code}${qs ? `?${qs}` : ""}`);
4547
+ return this.rest.get(`/invites/${encodeURIComponent(code)}${qs ? `?${qs}` : ""}`);
3523
4548
  }
3524
4549
  fetchSticker(id) {
3525
4550
  return this.rest.get(`/stickers/${id}`);
@@ -3528,14 +4553,14 @@ class Client extends EventEmitter2 {
3528
4553
  return this.rest.get(`/sticker-packs`);
3529
4554
  }
3530
4555
  fetchGuildTemplate(code) {
3531
- return this.rest.get(`/guilds/templates/${code}`);
4556
+ return this.rest.get(`/guilds/templates/${encodeURIComponent(code)}`);
3532
4557
  }
3533
4558
  generateInvite(options) {
3534
4559
  if (!this.user)
3535
4560
  throw new Error("Client must be ready to generate invite.");
3536
4561
  const params = new URLSearchParams({
3537
4562
  client_id: this.user.id,
3538
- scopes: (options?.scopes ?? ["bot"]).join(" ")
4563
+ scope: (options?.scopes ?? ["bot"]).join(" ")
3539
4564
  });
3540
4565
  if (options?.permissions) {
3541
4566
  params.set("permissions", String(options.permissions));
@@ -3547,9 +4572,11 @@ export {
3547
4572
  Client,
3548
4573
  ClientEvent,
3549
4574
  Collector,
4575
+ ClientEvent as Events,
3550
4576
  GatewayIntentBits,
3551
4577
  IntentBits,
3552
4578
  Intents,
4579
+ IntentsBitField,
3553
4580
  Permission,
3554
4581
  PermissionFlagsBits,
3555
4582
  PermissionOverwriteType,
@@ -3559,4 +4586,4 @@ export {
3559
4586
  resolveGatewayIntents
3560
4587
  };
3561
4588
 
3562
- //# debugId=5F8825194034E27864756E2164756E21
4589
+ //# debugId=B9E28396E97D857564756E2164756E21