lunibee 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +3 -3
  2. package/dist/builders/attachment.d.ts +8 -4
  3. package/dist/builders/commands.d.ts +122 -149
  4. package/dist/builders/components.d.ts +10 -150
  5. package/dist/builders/embed.d.ts +66 -37
  6. package/dist/builders/index.d.ts +0 -4
  7. package/dist/builders/index.js +112 -221
  8. package/dist/builders/index.js.map +10 -9
  9. package/dist/collection/cache.d.ts +15 -6
  10. package/dist/collection/index.d.ts +92 -17
  11. package/dist/collection/index.js +471 -279
  12. package/dist/collection/index.js.map +4 -4
  13. package/dist/core/api.d.ts +130 -0
  14. package/dist/core/channel-create.d.ts +72 -0
  15. package/dist/core/commands.d.ts +232 -0
  16. package/dist/core/events.d.ts +24 -7
  17. package/dist/core/handles.d.ts +157 -0
  18. package/dist/core/index.d.ts +68 -4
  19. package/dist/core/index.js +2639 -493
  20. package/dist/core/index.js.map +47 -37
  21. package/dist/core/permissions.d.ts +7 -7
  22. package/dist/formatters/index.js +9 -5
  23. package/dist/formatters/index.js.map +3 -3
  24. package/dist/handlers/index.js +5 -10
  25. package/dist/handlers/index.js.map +3 -3
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +9527 -7809
  28. package/dist/index.js.map +63 -53
  29. package/dist/managers/advanced.d.ts +29 -1
  30. package/dist/managers/base.d.ts +22 -14
  31. package/dist/managers/emoji.d.ts +0 -2
  32. package/dist/managers/guild-resources.d.ts +13 -5
  33. package/dist/managers/guild.d.ts +40 -4
  34. package/dist/managers/index.d.ts +21 -13
  35. package/dist/managers/index.js +969 -386
  36. package/dist/managers/index.js.map +30 -25
  37. package/dist/managers/member.d.ts +9 -2
  38. package/dist/managers/message.d.ts +10 -15
  39. package/dist/managers/role.d.ts +18 -10
  40. package/dist/managers/thread.d.ts +68 -0
  41. package/dist/managers/user.d.ts +18 -0
  42. package/dist/rest/errors.d.ts +14 -7
  43. package/dist/rest/index.d.ts +24 -12
  44. package/dist/rest/index.js +70 -20
  45. package/dist/rest/index.js.map +9 -9
  46. package/dist/rest/redis.d.ts +1 -1
  47. package/dist/rest/route.d.ts +8 -4
  48. package/dist/rest/routes.d.ts +122 -48
  49. package/dist/rest/transport.d.ts +10 -5
  50. package/dist/rest/webhook.d.ts +9 -5
  51. package/dist/sharding/bus.d.ts +47 -19
  52. package/dist/sharding/cluster.d.ts +11 -0
  53. package/dist/sharding/gateway-bot.d.ts +18 -0
  54. package/dist/sharding/index.d.ts +64 -25
  55. package/dist/sharding/index.js +439 -124
  56. package/dist/sharding/index.js.map +19 -14
  57. package/dist/sharding/supervisor.d.ts +32 -0
  58. package/dist/sharding/transport.d.ts +45 -0
  59. package/dist/structures/audit-log.d.ts +99 -2
  60. package/dist/structures/base.d.ts +40 -23
  61. package/dist/structures/channels.d.ts +45 -14
  62. package/dist/structures/index.d.ts +8 -12
  63. package/dist/structures/index.js +280 -132
  64. package/dist/structures/index.js.map +21 -15
  65. package/dist/structures/interactions.d.ts +91 -63
  66. package/dist/structures/resources.d.ts +99 -52
  67. package/dist/structures/sticker.d.ts +33 -0
  68. package/dist/types/channel-kind.d.ts +26 -0
  69. package/dist/types/gateway.d.ts +24 -12
  70. package/dist/types/index.d.ts +115 -113
  71. package/dist/types/index.js +53 -48
  72. package/dist/types/index.js.map +6 -5
  73. package/dist/voice/index.d.ts +12 -4
  74. package/dist/voice/index.js +14 -18
  75. package/dist/voice/index.js.map +3 -3
  76. package/dist/ws/decoder.d.ts +6 -2
  77. package/dist/ws/dispatcher.d.ts +19 -0
  78. package/dist/ws/index.d.ts +61 -15
  79. package/dist/ws/index.js +194 -77
  80. package/dist/ws/index.js.map +13 -11
  81. package/dist/ws/protocol.d.ts +10 -2
  82. package/dist/ws/reconnect.d.ts +10 -0
  83. package/dist/ws/send-budget.d.ts +6 -2
  84. package/dist/ws/session.d.ts +14 -4
  85. package/dist/ws/state.d.ts +22 -11
  86. package/package.json +20 -16
@@ -42,8 +42,12 @@ export declare class PlainTextDecoder implements GatewayDecoder {
42
42
  */
43
43
  export declare class ZlibStreamDecoder implements GatewayDecoder {
44
44
  #private;
45
- /** @param create Inflater factory; injected in tests. */
46
- constructor(create?: () => Inflate);
45
+ /**
46
+ * @param create Inflater factory; injected in tests.
47
+ * @param maxFrameBytes Largest inflated frame accepted (default 64 MiB);
48
+ * past it the stream fails instead of growing until memory runs out.
49
+ */
50
+ constructor(create?: () => Inflate, maxFrameBytes?: number);
47
51
  /**
48
52
  * A text frame on a compressed connection is already a complete frame, so
49
53
  * it is returned synchronously and never touches the inflater. Discord
@@ -0,0 +1,19 @@
1
+ /** Gateway event listener. */
2
+ export type GatewayListener = (data: unknown) => unknown;
3
+ /**
4
+ * Listener registry and emission for one Gateway.
5
+ *
6
+ * A listener that throws, or returns a promise that rejects, is isolated from
7
+ * the others and reported through `onFailure`, except for `error` listeners:
8
+ * a failing error listener must not report itself again.
9
+ */
10
+ export declare class GatewayDispatcher {
11
+ #private;
12
+ constructor(onFailure: (error: unknown) => void);
13
+ /** Adds a listener. */
14
+ on(event: string, listener: GatewayListener): void;
15
+ /** Removes a listener. */
16
+ off(event: string, listener: GatewayListener): void;
17
+ /** Calls the listeners of `event` with `data`. */
18
+ emit(event: string, data: unknown): void;
19
+ }
@@ -1,29 +1,48 @@
1
1
  import { type SocketFactory } from "./transport.js";
2
2
  import { type GatewayPayload, type GatewayProperties, type GatewayPresence, type GatewayIntentResolvable } from "@lunibee/types";
3
- export { classifyFrame, classifyPayload, identifyPayload, resumePayload, heartbeatPayload, type GatewayAction, type ProtocolResult, type ProtocolViolation, type IdentifyOptions, type IdentifyProperties, } from "./protocol.js";
3
+ export { classifyFrame, classifyPayload, identifyPayload, resumePayload, heartbeatPayload, type GatewayAction, type ProtocolResult, type ProtocolViolation, type IdentifyOptions, type IdentifyProperties, type IdentifyData, } from "./protocol.js";
4
4
  export { WebSocketTransport, type TransportHandlers, type TransportOptions, type SocketFactory, } from "./transport.js";
5
5
  export { createDecoder, PlainTextDecoder, ZlibStreamDecoder, type GatewayDecoder, } from "./decoder.js";
6
6
  export { GatewayReconnect, classifyCloseCode, FATAL_CLOSE_CODES, IDENTIFY_CLOSE_CODES, type CloseAction, type ReconnectOptions, type ScheduleResult, type ScheduleRefusal, } from "./reconnect.js";
7
7
  export { GatewayHeartbeat, type HeartbeatOptions, type HeartbeatTimeout, } from "./heartbeat.js";
8
8
  export { GatewaySession, type ResumeInfo, type HandshakeIntent, type InvalidationReason, } from "./session.js";
9
+ export { GatewayDispatcher, type GatewayListener } from "./dispatcher.js";
10
+ export { GatewaySendLimiter, SendBudget } from "./send-budget.js";
9
11
  export { GatewayOpcodes } from "./opcodes.js";
10
12
  export { GatewayCloseCodes } from "./close-codes.js";
11
13
  export { GatewayState, Status, GatewayError } from "./state.js";
12
14
  import { GatewayState } from "./state.js";
15
+ import { type GatewayListener } from "./dispatcher.js";
13
16
  /** Gateway connection configuration. */
14
17
  export interface GatewayOptions {
15
- /** Authentication token. */ token: string;
16
- /** Gateway intent bitfield or resolvable. */ intents: GatewayIntentResolvable;
17
- /** Shard identifier. */ shardId?: number;
18
- /** Total shard count. */ shardCount?: number;
19
- /** Whether automatic reconnect is enabled. */ reconnect?: boolean;
20
- /** Maximum reconnect attempts. */ maxReconnectAttempts?: number;
21
- /** Initial reconnect delay. */ reconnectBaseDelay?: number;
22
- /** Maximum reconnect delay. */ reconnectMaxDelay?: number;
23
- /** Heartbeat acknowledgement timeout. */ heartbeatAckTimeout?: number;
24
- /** Zombie connection timeout. */ zombieTimeout?: number;
25
- /** Identification properties. */ properties?: GatewayProperties;
26
- /** Presence data. */ presence?: GatewayPresence;
18
+ /** Authentication token. */
19
+ token: string;
20
+ /** Gateway intent bitfield or resolvable. */
21
+ intents: GatewayIntentResolvable;
22
+ /** Shard identifier. */
23
+ shardId?: number;
24
+ /** Total shard count. */
25
+ shardCount?: number;
26
+ /** Whether automatic reconnect is enabled. */
27
+ reconnect?: boolean;
28
+ /** Maximum reconnect attempts. */
29
+ maxReconnectAttempts?: number;
30
+ /** Initial reconnect delay. */
31
+ reconnectBaseDelay?: number;
32
+ /** Maximum reconnect delay. */
33
+ reconnectMaxDelay?: number;
34
+ /** Heartbeat acknowledgement timeout. */
35
+ heartbeatAckTimeout?: number;
36
+ /** Zombie connection timeout. */
37
+ zombieTimeout?: number;
38
+ /** Reconnects inside `reconnectStormWindow` after which the link counts as unstable: `unstable` is emitted and the delay keeps doubling. Default 5. */
39
+ reconnectStormLimit?: number;
40
+ /** The span over which reconnects are counted for storm detection, in ms. Default 60,000. */
41
+ reconnectStormWindow?: number;
42
+ /** Identification properties. */
43
+ properties?: GatewayProperties;
44
+ /** Presence data. */
45
+ presence?: GatewayPresence;
27
46
  /**
28
47
  * Whether to enable zlib-stream transport compression.
29
48
  * Decoded with a persistent `node:zlib` inflate stream, matching
@@ -37,8 +56,17 @@ export interface GatewayOptions {
37
56
  */
38
57
  createSocket?: SocketFactory;
39
58
  }
40
- /** Gateway event listener. */
41
- type GatewayListener = (data: unknown) => unknown;
59
+ /** What {@link Gateway.health} reports. */
60
+ export interface GatewayHealth {
61
+ /** Connection lifecycle state. */
62
+ state: GatewayState;
63
+ /** Round trip of the last acknowledged heartbeat in ms, or -1 before the first. */
64
+ latency: number;
65
+ /** Milliseconds since the last message from Discord, or 0 before any. */
66
+ silentFor: number;
67
+ /** Reconnects scheduled since this Gateway was created. */
68
+ reconnects: number;
69
+ }
42
70
  /** Manages a Discord Gateway connection. */
43
71
  export declare class Gateway {
44
72
  #private;
@@ -57,12 +85,30 @@ export declare class Gateway {
57
85
  on(event: string, listener: GatewayListener): this;
58
86
  /** Removes a Gateway event listener. @param event Event name. @param listener Event callback. @returns This Gateway. */
59
87
  off(event: string, listener: GatewayListener): this;
88
+ /**
89
+ * A snapshot for dashboards and health checks: connection state, last
90
+ * heartbeat round trip, how long the link has been silent, and how many
91
+ * times it has reconnected.
92
+ */
93
+ health(): GatewayHealth;
60
94
  /** Sends a Gateway payload. @param payload Gateway payload. @returns Whether it was sent. */
61
95
  send(payload: GatewayPayload): boolean;
62
96
  /** Sends a presence update. @param data Presence payload. @returns Whether it was sent. */
63
97
  setPresence(data: GatewayPresence): boolean;
64
98
  /** Sends a voice state update. @param data Voice state payload. @returns Whether it was sent. */
65
99
  setVoiceState(data: Record<string, unknown>): boolean;
100
+ /**
101
+ * Joins, moves or leaves a voice channel (op 4). Returns `false` without
102
+ * sending when the guild belongs to another shard, or when disconnected.
103
+ * @param guildId Guild snowflake. @param channelId Channel to join, or `null` to leave.
104
+ * @param options `selfMute` and `selfDeaf`, both default `false`. @returns Whether it was sent.
105
+ */
106
+ sendVoiceState(guildId: string, channelId: string | null, options?: {
107
+ selfMute?: boolean;
108
+ selfDeaf?: boolean;
109
+ }): boolean;
110
+ /** Whether `guildId` maps to this Gateway's shard (`(id >> 22) % shardCount === shardId`). */
111
+ ownsGuild(guildId: string): boolean;
66
112
  /** Requests guild members. @param data Guild member request payload. @returns Whether it was sent. */
67
113
  requestGuildMembers(data: Record<string, unknown>): boolean;
68
114
  }
package/dist/ws/index.js CHANGED
@@ -73,7 +73,26 @@ class GatewaySession {
73
73
  return resume ? { type: "resume", ...resume } : { type: "identify" };
74
74
  }
75
75
  connectURL(fallback) {
76
- return this.resumeInfo()?.resumeURL ?? fallback;
76
+ const resume = this.resumeInfo()?.resumeURL;
77
+ return resume !== undefined && isTrustedGatewayURL(resume, fallback) ? resume : fallback;
78
+ }
79
+ }
80
+ function isTrustedGatewayURL(url, fallback) {
81
+ let parsed;
82
+ try {
83
+ parsed = new URL(url);
84
+ } catch {
85
+ return false;
86
+ }
87
+ if (parsed.protocol !== "wss:" || parsed.username || parsed.password)
88
+ return false;
89
+ const host = parsed.hostname.toLowerCase();
90
+ if (host === "discord.gg" || host.endsWith(".discord.gg"))
91
+ return true;
92
+ try {
93
+ return host === new URL(fallback).hostname.toLowerCase();
94
+ } catch {
95
+ return false;
77
96
  }
78
97
  }
79
98
 
@@ -245,6 +264,7 @@ class GatewayReconnect {
245
264
  #options;
246
265
  #random;
247
266
  #attempt = 0;
267
+ #recent = [];
248
268
  #timer;
249
269
  #promise;
250
270
  #resolve;
@@ -285,14 +305,29 @@ class GatewayReconnect {
285
305
  if (this.exhausted)
286
306
  return { scheduled: false, reason: "exhausted" };
287
307
  const attempt = this.#attempt;
288
- const delay = Math.min(this.#options.maxDelay, this.#options.baseDelay * 2 ** this.#attempt++);
308
+ const now = (this.#options.now ?? Date.now)();
309
+ const window = this.#options.stormWindow ?? 60000;
310
+ const limit = this.#options.stormLimit ?? 5;
311
+ while (this.#recent.length && now - this.#recent[0] >= window)
312
+ this.#recent.shift();
313
+ const over = this.#recent.length - limit;
314
+ this.#recent.push(now);
315
+ let delay = Math.min(this.#options.maxDelay, this.#options.baseDelay * 2 ** this.#attempt++);
316
+ const unstable = over >= 0;
317
+ if (unstable)
318
+ delay = Math.min(this.#options.maxDelay, Math.max(delay, this.#options.baseDelay * 2 ** (over + 1)));
289
319
  const jitter = this.#random() * Math.max(1, delay * 0.25);
290
320
  const delayMs = delay + jitter;
291
321
  this.#timer = setTimeout(() => {
292
322
  this.#timer = undefined;
293
323
  run();
294
324
  }, delayMs);
295
- return { scheduled: true, delayMs, attempt };
325
+ return {
326
+ scheduled: true,
327
+ delayMs,
328
+ attempt,
329
+ ...unstable ? { unstable: { reconnects: this.#recent.length } } : {}
330
+ };
296
331
  }
297
332
  cancel() {
298
333
  if (this.#timer)
@@ -349,12 +384,15 @@ class PlainTextDecoder {
349
384
 
350
385
  class ZlibStreamDecoder {
351
386
  #create;
387
+ #maxFrameBytes;
352
388
  #inflate;
353
389
  #chunks = [];
390
+ #size = 0;
354
391
  #failure;
355
392
  #pendingReject;
356
- constructor(create = () => createInflate()) {
393
+ constructor(create = () => createInflate(), maxFrameBytes = 64 * 1024 * 1024) {
357
394
  this.#create = create;
395
+ this.#maxFrameBytes = maxFrameBytes;
358
396
  }
359
397
  push(data) {
360
398
  if (typeof data === "string")
@@ -373,6 +411,7 @@ class ZlibStreamDecoder {
373
411
  await this.#awaiting((settle) => inflate.flush(zlibConstants.Z_SYNC_FLUSH, () => settle()));
374
412
  const parts = this.#chunks;
375
413
  this.#chunks = [];
414
+ this.#size = 0;
376
415
  if (parts.length === 0)
377
416
  return [];
378
417
  const total = parts.reduce((size, part) => size + part.length, 0);
@@ -388,6 +427,7 @@ class ZlibStreamDecoder {
388
427
  const inflate = this.#inflate;
389
428
  this.#inflate = undefined;
390
429
  this.#chunks = [];
430
+ this.#size = 0;
391
431
  this.#failure = undefined;
392
432
  this.#pendingReject = undefined;
393
433
  if (!inflate)
@@ -415,7 +455,17 @@ class ZlibStreamDecoder {
415
455
  }
416
456
  #open() {
417
457
  const inflate = this.#create();
418
- inflate.on("data", (chunk) => this.#chunks.push(chunk));
458
+ inflate.on("data", (chunk) => {
459
+ if (this.#failure)
460
+ return;
461
+ this.#size += chunk.length;
462
+ if (this.#size > this.#maxFrameBytes) {
463
+ this.#chunks = [];
464
+ inflate.emit("error", new Error(`Gateway frame inflated past ${this.#maxFrameBytes} bytes.`));
465
+ return;
466
+ }
467
+ this.#chunks.push(chunk);
468
+ });
419
469
  inflate.on("error", (error) => {
420
470
  this.#failure = error;
421
471
  const reject = this.#pendingReject;
@@ -658,9 +708,9 @@ function classifyPayload(payload) {
658
708
  }
659
709
  function identifyPayload(options) {
660
710
  const props = options.properties ?? {};
661
- const os = props.os ?? "Android";
662
- const browser = props.browser ?? "Discord Android";
663
- const device = props.device ?? "Discord Android";
711
+ const os = props.os ?? process.platform;
712
+ const browser = props.browser ?? "Lunibee";
713
+ const device = props.device ?? "Lunibee";
664
714
  return {
665
715
  op: GatewayOpcodes.Identify,
666
716
  d: {
@@ -703,6 +753,21 @@ function heartbeatPayload(sequence) {
703
753
  return { op: GatewayOpcodes.Heartbeat, d: sequence, s: null, t: null };
704
754
  }
705
755
 
756
+ // packages/types/src/channel-kind.ts
757
+ var ChannelKinds = {
758
+ text: [0],
759
+ dm: [1],
760
+ voice: [2],
761
+ "group-dm": [3],
762
+ category: [4],
763
+ announcement: [5],
764
+ thread: [10, 11, 12],
765
+ stage: [13],
766
+ directory: [14],
767
+ forum: [15],
768
+ media: [16]
769
+ };
770
+ var KIND_OF_TYPE = new Map(Object.keys(ChannelKinds).flatMap((kind) => ChannelKinds[kind].map((type) => [type, kind])));
706
771
  // packages/types/src/gateway.ts
707
772
  var GatewayIntentBits = {
708
773
  Guilds: 1 << 0,
@@ -756,52 +821,59 @@ var IntentBits = {
756
821
  directMessagePolls: 1 << 25
757
822
  };
758
823
  function resolveGatewayIntents(intents) {
759
- if (typeof intents === "number")
760
- return intents;
761
- if (intents && typeof intents === "object" && !Array.isArray(intents))
762
- return intents.bitfield;
763
- if (Array.isArray(intents)) {
764
- return intents.reduce((acc, intent) => {
765
- return acc | resolveGatewayIntents(intent);
766
- }, 0);
767
- }
768
- if (typeof intents === "string") {
769
- if (intents in GatewayIntentBits)
770
- return GatewayIntentBits[intents];
771
- if (intents in IntentBits)
772
- return IntentBits[intents];
773
- const lower = intents.charAt(0).toLowerCase() + intents.slice(1);
774
- if (lower in IntentBits)
775
- return IntentBits[lower];
776
- const num = Number(intents);
777
- if (!Number.isNaN(num))
778
- return num;
779
- }
780
- return 0;
824
+ if (!Array.isArray(intents))
825
+ throw new TypeError("Gateway intents must be an array, e.g. [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages].");
826
+ return intents.reduce((acc, intent) => acc | resolveIntent(intent), 0);
781
827
  }
782
- // packages/ws/src/state.ts
783
- var GatewayState;
784
- ((GatewayState) => {
785
- GatewayState["Connect"] = "CONNECT";
786
- GatewayState["Hello"] = "HELLO";
787
- GatewayState["Identify"] = "IDENTIFY";
788
- GatewayState["Resume"] = "RESUME";
789
- GatewayState["Ready"] = "READY";
790
- GatewayState["Dispatch"] = "DISPATCH";
791
- GatewayState["Heartbeat"] = "HEARTBEAT";
792
- GatewayState["Reconnect"] = "RECONNECT";
793
- GatewayState["Closed"] = "CLOSED";
794
- })(GatewayState ||= {});
795
- class GatewayError extends Error {
796
- code;
797
- constructor(message, code, options) {
798
- super(message, options);
799
- this.name = "GatewayError";
800
- this.code = code;
828
+ function resolveIntent(intent) {
829
+ if (typeof intent === "number")
830
+ return intent;
831
+ const bits = {
832
+ ...IntentBits,
833
+ ...GatewayIntentBits
834
+ };
835
+ const lower = intent.charAt(0).toLowerCase() + intent.slice(1);
836
+ if (Object.hasOwn(bits, intent))
837
+ return bits[intent];
838
+ if (Object.hasOwn(bits, lower))
839
+ return bits[lower];
840
+ const num = Number(intent);
841
+ return Number.isNaN(num) ? 0 : num;
842
+ }
843
+ // packages/ws/src/dispatcher.ts
844
+ class GatewayDispatcher {
845
+ #listeners = new Map;
846
+ #onFailure;
847
+ constructor(onFailure) {
848
+ this.#onFailure = onFailure;
849
+ }
850
+ on(event, listener) {
851
+ let listeners = this.#listeners.get(event);
852
+ if (!listeners)
853
+ this.#listeners.set(event, listeners = new Set);
854
+ listeners.add(listener);
855
+ }
856
+ off(event, listener) {
857
+ this.#listeners.get(event)?.delete(listener);
858
+ }
859
+ emit(event, data) {
860
+ for (const listener of this.#listeners.get(event) ?? []) {
861
+ try {
862
+ const result = listener(data);
863
+ if (result && typeof result.then === "function")
864
+ Promise.resolve(result).catch((error) => {
865
+ if (event !== "error")
866
+ this.#onFailure(error);
867
+ });
868
+ } catch (error) {
869
+ if (event !== "error")
870
+ this.#onFailure(error);
871
+ }
872
+ }
801
873
  }
802
874
  }
803
875
  // packages/ws/src/send-budget.ts
804
- class SendBudget {
876
+ class GatewaySendLimiter {
805
877
  #timestamps = [];
806
878
  #limit;
807
879
  #windowMs;
@@ -821,7 +893,28 @@ class SendBudget {
821
893
  this.#timestamps.push(now);
822
894
  }
823
895
  }
824
-
896
+ var SendBudget = GatewaySendLimiter;
897
+ // packages/ws/src/state.ts
898
+ var GatewayState;
899
+ ((GatewayState) => {
900
+ GatewayState["Connect"] = "CONNECT";
901
+ GatewayState["Hello"] = "HELLO";
902
+ GatewayState["Identify"] = "IDENTIFY";
903
+ GatewayState["Resume"] = "RESUME";
904
+ GatewayState["Ready"] = "READY";
905
+ GatewayState["Dispatch"] = "DISPATCH";
906
+ GatewayState["Heartbeat"] = "HEARTBEAT";
907
+ GatewayState["Reconnect"] = "RECONNECT";
908
+ GatewayState["Closed"] = "CLOSED";
909
+ })(GatewayState ||= {});
910
+ class GatewayError extends Error {
911
+ code;
912
+ constructor(message, code, options) {
913
+ super(message, options);
914
+ this.name = "GatewayError";
915
+ this.code = code;
916
+ }
917
+ }
825
918
  // packages/ws/src/index.ts
826
919
  var RESUMABLE_CLOSE_CODE = 4900;
827
920
  var DEFAULT_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
@@ -835,8 +928,9 @@ class Gateway {
835
928
  #heartbeat;
836
929
  #closed = false;
837
930
  #reconnect;
838
- #listeners = new Map;
839
- #sendBudget = new SendBudget;
931
+ #events = new GatewayDispatcher((error) => this.#emitError(error));
932
+ #sendBudget = new GatewaySendLimiter;
933
+ #reconnects = 0;
840
934
  ping = -1;
841
935
  constructor(options) {
842
936
  if (!options.token?.trim())
@@ -854,9 +948,9 @@ class Gateway {
854
948
  heartbeatAckTimeout: 1e4,
855
949
  zombieTimeout: 30000,
856
950
  properties: {
857
- os: "Android",
858
- browser: "Discord Android",
859
- device: "Discord Android"
951
+ os: process.platform,
952
+ browser: "Lunibee",
953
+ device: "Lunibee"
860
954
  },
861
955
  presence: {
862
956
  status: "online",
@@ -887,7 +981,9 @@ class Gateway {
887
981
  enabled: this.#options.reconnect,
888
982
  maxAttempts: this.#options.maxReconnectAttempts,
889
983
  baseDelay: this.#options.reconnectBaseDelay,
890
- maxDelay: this.#options.reconnectMaxDelay
984
+ maxDelay: this.#options.reconnectMaxDelay,
985
+ stormLimit: options.reconnectStormLimit,
986
+ stormWindow: options.reconnectStormWindow
891
987
  });
892
988
  this.#heartbeat = new GatewayHeartbeat({
893
989
  ackTimeout: this.#options.heartbeatAckTimeout,
@@ -938,16 +1034,21 @@ class Gateway {
938
1034
  on(event, listener) {
939
1035
  if (!event || typeof listener !== "function")
940
1036
  throw new TypeError("Gateway event and listener are required.");
941
- let listeners = this.#listeners.get(event);
942
- if (!listeners)
943
- this.#listeners.set(event, listeners = new Set);
944
- listeners.add(listener);
1037
+ this.#events.on(event, listener);
945
1038
  return this;
946
1039
  }
947
1040
  off(event, listener) {
948
- this.#listeners.get(event)?.delete(listener);
1041
+ this.#events.off(event, listener);
949
1042
  return this;
950
1043
  }
1044
+ health() {
1045
+ return {
1046
+ state: this.state,
1047
+ latency: this.#heartbeat.latency,
1048
+ silentFor: this.#heartbeat.silentFor,
1049
+ reconnects: this.#reconnects
1050
+ };
1051
+ }
951
1052
  send(payload) {
952
1053
  if (!payload || !Number.isInteger(payload.op))
953
1054
  throw new TypeError("Gateway payload must contain an integer opcode.");
@@ -989,6 +1090,22 @@ class Gateway {
989
1090
  t: null
990
1091
  });
991
1092
  }
1093
+ sendVoiceState(guildId, channelId, options = {}) {
1094
+ if (!this.ownsGuild(guildId))
1095
+ return false;
1096
+ return this.setVoiceState({
1097
+ guild_id: guildId,
1098
+ channel_id: channelId,
1099
+ self_mute: options.selfMute ?? false,
1100
+ self_deaf: options.selfDeaf ?? false
1101
+ });
1102
+ }
1103
+ ownsGuild(guildId) {
1104
+ const count = this.#options.shardCount;
1105
+ if (count <= 1)
1106
+ return true;
1107
+ return Number((BigInt(guildId) >> 22n) % BigInt(count)) === this.#options.shardId;
1108
+ }
992
1109
  requestGuildMembers(data) {
993
1110
  return this.send({
994
1111
  op: GatewayOpcodes.RequestGuildMembers,
@@ -1126,7 +1243,16 @@ class Gateway {
1126
1243
  if (this.#closed)
1127
1244
  return;
1128
1245
  const result = this.#reconnect.schedule(() => this.#open(this.#session.connectURL(DEFAULT_GATEWAY_URL)));
1129
- if (result.scheduled || result.reason !== "exhausted")
1246
+ if (result.scheduled) {
1247
+ this.#reconnects++;
1248
+ if (result.unstable)
1249
+ this.#emit("unstable", {
1250
+ reconnects: result.unstable.reconnects,
1251
+ delay: result.delayMs
1252
+ });
1253
+ return;
1254
+ }
1255
+ if (result.reason !== "exhausted")
1130
1256
  return;
1131
1257
  this.#reconnect.settle(new GatewayError("Gateway reconnect attempts exhausted."));
1132
1258
  this.#setState("CONNECT" /* Connect */);
@@ -1147,19 +1273,7 @@ class Gateway {
1147
1273
  this.#emit("error", error instanceof Error ? error : new GatewayError(String(error)));
1148
1274
  }
1149
1275
  #emit(event, data) {
1150
- for (const listener of this.#listeners.get(event) ?? []) {
1151
- try {
1152
- const result = listener(data);
1153
- if (result && typeof result.then === "function")
1154
- Promise.resolve(result).catch((error) => {
1155
- if (event !== "error")
1156
- this.#emitError(error);
1157
- });
1158
- } catch (error) {
1159
- if (event !== "error")
1160
- this.#emitError(error);
1161
- }
1162
- }
1276
+ this.#events.emit(event, data);
1163
1277
  }
1164
1278
  #setState(next) {
1165
1279
  if (this.state === next)
@@ -1173,14 +1287,17 @@ export {
1173
1287
  FATAL_CLOSE_CODES,
1174
1288
  Gateway,
1175
1289
  GatewayCloseCodes,
1290
+ GatewayDispatcher,
1176
1291
  GatewayError,
1177
1292
  GatewayHeartbeat,
1178
1293
  GatewayOpcodes,
1179
1294
  GatewayReconnect,
1295
+ GatewaySendLimiter,
1180
1296
  GatewaySession,
1181
1297
  GatewayState,
1182
1298
  IDENTIFY_CLOSE_CODES,
1183
1299
  PlainTextDecoder,
1300
+ SendBudget,
1184
1301
  GatewayState as Status,
1185
1302
  WebSocketTransport,
1186
1303
  ZlibStreamDecoder,
@@ -1193,4 +1310,4 @@ export {
1193
1310
  resumePayload
1194
1311
  };
1195
1312
 
1196
- //# debugId=3B9E4067E44200CD64756E2164756E21
1313
+ //# debugId=BCA247EAFCA9772B64756E2164756E21