lunibee 0.1.8 → 0.2.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 (44) hide show
  1. package/README.md +2 -0
  2. package/dist/builders/components.d.ts +5 -0
  3. package/dist/builders/index.js +72 -12
  4. package/dist/builders/index.js.map +5 -5
  5. package/dist/collection/index.d.ts +68 -1
  6. package/dist/collection/index.js +334 -1
  7. package/dist/collection/index.js.map +3 -3
  8. package/dist/core/collector.d.ts +13 -0
  9. package/dist/core/events.d.ts +29 -3
  10. package/dist/core/index.d.ts +22 -8
  11. package/dist/core/index.js +1526 -124
  12. package/dist/core/index.js.map +26 -22
  13. package/dist/core/permissions.d.ts +48 -0
  14. package/dist/index.js +1578 -268
  15. package/dist/index.js.map +30 -29
  16. package/dist/managers/advanced.d.ts +116 -0
  17. package/dist/managers/base.d.ts +13 -2
  18. package/dist/managers/emoji.d.ts +1 -1
  19. package/dist/managers/guild.d.ts +28 -2
  20. package/dist/managers/index.d.ts +37 -5
  21. package/dist/managers/index.js +870 -152
  22. package/dist/managers/index.js.map +18 -16
  23. package/dist/managers/message.d.ts +10 -1
  24. package/dist/rest/errors.d.ts +33 -0
  25. package/dist/rest/index.d.ts +1 -1
  26. package/dist/rest/index.js +52 -6
  27. package/dist/rest/index.js.map +5 -5
  28. package/dist/rest/routes.d.ts +20 -0
  29. package/dist/sharding/index.d.ts +29 -0
  30. package/dist/sharding/index.js +99 -15
  31. package/dist/sharding/index.js.map +8 -8
  32. package/dist/structures/base.d.ts +2 -0
  33. package/dist/structures/index.js +96 -42
  34. package/dist/structures/index.js.map +10 -9
  35. package/dist/structures/interactions.d.ts +26 -0
  36. package/dist/types/gateway.d.ts +16 -0
  37. package/dist/types/index.d.ts +55 -1
  38. package/dist/types/index.js.map +2 -2
  39. package/dist/voice/index.d.ts +3 -0
  40. package/dist/voice/index.js +15 -1
  41. package/dist/voice/index.js.map +3 -3
  42. package/dist/ws/index.js +13 -5
  43. package/dist/ws/index.js.map +6 -6
  44. package/package.json +14 -14
@@ -1,4 +1,19 @@
1
1
  // @bun
2
+ // packages/sharding/package.json
3
+ var package_default = {
4
+ name: "@lunibee/sharding",
5
+ version: "0.2.0",
6
+ type: "module",
7
+ module: "./src/index.ts",
8
+ types: "./src/index.ts",
9
+ exports: {
10
+ ".": "./src/index.ts"
11
+ },
12
+ dependencies: {
13
+ "@lunibee/ws": "workspace:*"
14
+ }
15
+ };
16
+
2
17
  // packages/ws/src/session.ts
3
18
  class GatewaySession {
4
19
  #state = { status: "none" };
@@ -34,6 +49,8 @@ class GatewaySession {
34
49
  return false;
35
50
  if (!Number.isInteger(sequence))
36
51
  return false;
52
+ if (this.#sequence !== null && sequence < this.#sequence)
53
+ return false;
37
54
  this.#sequence = sequence;
38
55
  return true;
39
56
  }
@@ -132,8 +149,11 @@ class GatewayHeartbeat {
132
149
  this.#clearBeatTimers();
133
150
  this.#interval = interval;
134
151
  this.#acknowledged = true;
135
- this.#initialTimer = setTimeout(() => this.sendHeartbeat(), Math.random() * interval);
136
- this.#timer = setInterval(() => this.sendHeartbeat(), interval);
152
+ this.#initialTimer = setTimeout(() => {
153
+ this.#initialTimer = undefined;
154
+ this.sendHeartbeat();
155
+ this.#timer = setInterval(() => this.sendHeartbeat(), interval);
156
+ }, Math.random() * interval);
137
157
  }
138
158
  stop() {
139
159
  this.#clearBeatTimers();
@@ -159,6 +179,8 @@ class GatewayHeartbeat {
159
179
  }, this.#options.ackTimeout);
160
180
  }
161
181
  acknowledge() {
182
+ if (this.#acknowledged)
183
+ return;
162
184
  this.#acknowledged = true;
163
185
  this.#latency = this.#now() - this.#sentAt;
164
186
  this.#clearAckTimer();
@@ -804,6 +826,7 @@ class SendBudget {
804
826
  }
805
827
 
806
828
  // packages/ws/src/index.ts
829
+ var RESUMABLE_CLOSE_CODE = 4900;
807
830
  var DEFAULT_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
808
831
 
809
832
  class Gateway {
@@ -889,7 +912,7 @@ class Gateway {
889
912
  } else {
890
913
  this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${timeout.elapsedMs}ms.`));
891
914
  }
892
- this.#transport.close(1001, timeout.type === "zombie" ? "Zombie Gateway connection" : "Heartbeat timeout");
915
+ this.#transport.close(RESUMABLE_CLOSE_CODE, timeout.type === "zombie" ? "Zombie Gateway connection" : "Heartbeat timeout");
893
916
  }
894
917
  connect(url = DEFAULT_GATEWAY_URL) {
895
918
  if (this.state === "CLOSED" /* Closed */)
@@ -1025,7 +1048,7 @@ class Gateway {
1025
1048
  this.#emit("heartbeatAck", action.data);
1026
1049
  return;
1027
1050
  case "reconnect":
1028
- this.#transport.close(1001, "Server requested reconnect");
1051
+ this.#transport.close(RESUMABLE_CLOSE_CODE, "Server requested reconnect");
1029
1052
  return;
1030
1053
  case "invalid-session": {
1031
1054
  if (!action.resumable)
@@ -1326,7 +1349,7 @@ class ClusterManager {
1326
1349
  const response = await fetch("https://discord.com/api/v10/gateway/bot", {
1327
1350
  headers: {
1328
1351
  Authorization: `Bot ${this.#options.token}`,
1329
- "User-Agent": "Lunibee/0.1.0"
1352
+ "User-Agent": `Lunibee/${package_default.version}`
1330
1353
  }
1331
1354
  });
1332
1355
  if (!response.ok) {
@@ -1477,6 +1500,34 @@ class ClusterManager {
1477
1500
 
1478
1501
  // packages/sharding/src/index.ts
1479
1502
  var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1503
+ function needsConnection(gateway) {
1504
+ return gateway.state === "CONNECT" /* Connect */ || gateway.state === "RECONNECT" /* Reconnect */ || gateway.state === "CLOSED" /* Closed */;
1505
+ }
1506
+ var HANDSHAKE_SENT = new Set([
1507
+ "IDENTIFY" /* Identify */,
1508
+ "RESUME" /* Resume */,
1509
+ "READY" /* Ready */,
1510
+ "DISPATCH" /* Dispatch */,
1511
+ "HEARTBEAT" /* Heartbeat */,
1512
+ "CLOSED" /* Closed */
1513
+ ]);
1514
+ function handshakeSent(gateway, timeout) {
1515
+ if (HANDSHAKE_SENT.has(gateway.state))
1516
+ return Promise.resolve();
1517
+ return new Promise((resolve) => {
1518
+ const done = () => {
1519
+ clearTimeout(timer);
1520
+ gateway.off("stateChange", onState);
1521
+ resolve();
1522
+ };
1523
+ const onState = (change) => {
1524
+ if (HANDSHAKE_SENT.has(change.next))
1525
+ done();
1526
+ };
1527
+ const timer = setTimeout(done, timeout);
1528
+ gateway.on("stateChange", onState);
1529
+ });
1530
+ }
1480
1531
 
1481
1532
  class ShardManager {
1482
1533
  static IDENTIFY_INTERVAL = 5000;
@@ -1489,6 +1540,7 @@ class ShardManager {
1489
1540
  #destroyed = false;
1490
1541
  #auto;
1491
1542
  #autoScaleTimer;
1543
+ #gatewayInfo;
1492
1544
  constructor(options) {
1493
1545
  if (!options.token?.trim())
1494
1546
  throw new TypeError("A Discord bot token is required.");
@@ -1501,6 +1553,10 @@ class ShardManager {
1501
1553
  throw new RangeError("autoScaleInterval must be an integer of at least 1000 milliseconds.");
1502
1554
  if (options.spawnDelay !== undefined && (!Number.isFinite(options.spawnDelay) || options.spawnDelay < 0))
1503
1555
  throw new RangeError("spawnDelay must be a non-negative finite number of milliseconds.");
1556
+ if (options.maxConcurrency !== undefined && (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency < 1))
1557
+ throw new RangeError("maxConcurrency must be a positive integer.");
1558
+ if (options.handshakeTimeout !== undefined && (!Number.isFinite(options.handshakeTimeout) || options.handshakeTimeout < 0))
1559
+ throw new RangeError("handshakeTimeout must be a non-negative number of milliseconds.");
1504
1560
  this.#auto = options.shardCount === "auto";
1505
1561
  this.#options = {
1506
1562
  spawnDelay: ShardManager.IDENTIFY_INTERVAL,
@@ -1510,10 +1566,13 @@ class ShardManager {
1510
1566
  this.#initialize(count);
1511
1567
  }
1512
1568
  async fetchRecommendedShardCount() {
1569
+ return (await this.fetchGatewayInfo()).shards;
1570
+ }
1571
+ async fetchGatewayInfo() {
1513
1572
  const response = await fetch("https://discord.com/api/v10/gateway/bot", {
1514
1573
  headers: {
1515
1574
  Authorization: `Bot ${this.#options.token}`,
1516
- "User-Agent": "Lunibee/0.1.0"
1575
+ "User-Agent": `Lunibee/${package_default.version}`
1517
1576
  }
1518
1577
  });
1519
1578
  if (!response.ok)
@@ -1521,7 +1580,17 @@ class ShardManager {
1521
1580
  const data = await response.json();
1522
1581
  if (typeof data.shards !== "number" || !Number.isInteger(data.shards) || data.shards < 1)
1523
1582
  throw new Error("Gateway discovery returned an invalid shard count.");
1524
- return data.shards;
1583
+ const limit = data.session_start_limit;
1584
+ const info = { shards: data.shards };
1585
+ if (limit && typeof limit.total === "number" && typeof limit.remaining === "number" && typeof limit.reset_after === "number" && typeof limit.max_concurrency === "number")
1586
+ info.sessionStartLimit = {
1587
+ total: limit.total,
1588
+ remaining: limit.remaining,
1589
+ resetAfter: limit.reset_after,
1590
+ maxConcurrency: Math.max(1, limit.max_concurrency)
1591
+ };
1592
+ this.#gatewayInfo = info;
1593
+ return info;
1525
1594
  }
1526
1595
  async connect() {
1527
1596
  if (this.#autoScaleTimer) {
@@ -1529,17 +1598,25 @@ class ShardManager {
1529
1598
  this.#autoScaleTimer = undefined;
1530
1599
  }
1531
1600
  await this.#ensureInitialized();
1532
- for (const [id, shard] of this.shards) {
1533
- try {
1534
- await shard.connect();
1535
- } catch (error) {
1601
+ const limit = this.#gatewayInfo?.sessionStartLimit;
1602
+ const ids = [...this.shards].filter(([, gateway]) => needsConnection(gateway)).map(([id]) => id);
1603
+ if (limit && limit.remaining < ids.length)
1604
+ throw new Error(`Session start limit exhausted: ${limit.remaining} of ${limit.total} IDENTIFYs left for ${ids.length} shards; resets in ${Math.ceil(limit.resetAfter / 1000)}s.`);
1605
+ const concurrency = Math.min(this.#options.maxConcurrency ?? limit?.maxConcurrency ?? 1, limit?.maxConcurrency ?? Infinity);
1606
+ for (let start = 0;start < ids.length; start += concurrency) {
1607
+ const round = ids.slice(start, start + concurrency);
1608
+ const results = await Promise.allSettled(round.map((id) => this.shards.get(id).connect()));
1609
+ const failed = results.findIndex((result) => result.status === "rejected");
1610
+ if (failed !== -1) {
1536
1611
  this.destroy();
1537
- throw new Error(`Failed to connect shard ${id}.`, {
1538
- cause: error
1612
+ throw new Error(`Failed to connect shard ${round[failed]}.`, {
1613
+ cause: results[failed].reason
1539
1614
  });
1540
1615
  }
1541
- if (id + 1 < this.shardCount && this.#options.spawnDelay && this.#options.spawnDelay > 0)
1616
+ if (start + concurrency < ids.length && this.#options.spawnDelay && this.#options.spawnDelay > 0) {
1617
+ await Promise.all(round.map((id) => handshakeSent(this.shards.get(id), this.#options.handshakeTimeout ?? 15000)));
1542
1618
  await sleep2(this.#options.spawnDelay);
1619
+ }
1543
1620
  }
1544
1621
  if (this.#options.autoScaleInterval && !this.#autoScaleTimer) {
1545
1622
  this.#autoScaleTimer = setInterval(() => {
@@ -1582,6 +1659,13 @@ class ShardManager {
1582
1659
  get(id) {
1583
1660
  return this.shards.get(id);
1584
1661
  }
1662
+ health() {
1663
+ return [...this.shards].map(([id, gateway]) => ({
1664
+ id,
1665
+ state: gateway.state,
1666
+ ping: gateway.ping
1667
+ }));
1668
+ }
1585
1669
  values() {
1586
1670
  return [...this.shards].map(([id, gateway]) => ({ id, gateway }));
1587
1671
  }
@@ -1617,4 +1701,4 @@ export {
1617
1701
  ShardManager as ShardingManager
1618
1702
  };
1619
1703
 
1620
- //# debugId=C1062D33C7999C8E64756E2164756E21
1704
+ //# debugId=80E4CAFE56ABED2364756E2164756E21