lunibee 0.1.5 → 0.1.7
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.
- package/README.md +13 -24
- package/dist/builders/commands.d.ts +37 -0
- package/dist/builders/components.d.ts +127 -12
- package/dist/builders/embed.d.ts +3 -1
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +319 -16
- package/dist/builders/index.js.map +8 -7
- package/dist/collection/index.d.ts +36 -0
- package/dist/collection/index.js +110 -6
- package/dist/collection/index.js.map +4 -4
- package/dist/core/events.d.ts +69 -3
- package/dist/core/index.d.ts +158 -90
- package/dist/core/index.js +1543 -1169
- package/dist/core/index.js.map +24 -23
- package/dist/core/permissions.d.ts +112 -53
- package/dist/formatters/index.d.ts +43 -3
- package/dist/formatters/index.js +64 -1
- package/dist/formatters/index.js.map +3 -3
- package/dist/handlers/index.js.map +2 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3882 -1588
- package/dist/index.js.map +43 -35
- package/dist/managers/emoji.d.ts +38 -0
- package/dist/managers/guild.d.ts +141 -0
- package/dist/managers/index.d.ts +86 -75
- package/dist/managers/index.js +1084 -67
- package/dist/managers/index.js.map +21 -16
- package/dist/managers/message.d.ts +7 -12
- package/dist/managers/user.d.ts +20 -0
- package/dist/rest/index.d.ts +173 -1
- package/dist/rest/index.js +343 -47
- package/dist/rest/index.js.map +8 -6
- package/dist/rest/redis.d.ts +37 -0
- package/dist/rest/routes.d.ts +26 -1
- package/dist/rest/store.d.ts +38 -0
- package/dist/rest/webhook.d.ts +2 -2
- package/dist/sharding/cluster.d.ts +61 -0
- package/dist/sharding/index.d.ts +8 -0
- package/dist/sharding/index.js +289 -18
- package/dist/sharding/index.js.map +8 -7
- package/dist/structures/audit-log.d.ts +26 -0
- package/dist/structures/base.d.ts +131 -48
- package/dist/structures/embed.d.ts +65 -0
- package/dist/structures/index.d.ts +74 -26
- package/dist/structures/index.js +1528 -59
- package/dist/structures/index.js.map +14 -8
- package/dist/structures/interactions.d.ts +72 -17
- package/dist/structures/resources.d.ts +199 -7
- package/dist/types/index.d.ts +605 -84
- package/dist/types/index.js +71 -1
- package/dist/types/index.js.map +3 -3
- package/dist/utils/index.js.map +2 -2
- package/dist/voice/index.d.ts +88 -0
- package/dist/voice/index.js +204 -5
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/index.d.ts +40 -1
- package/dist/ws/index.js +105 -18
- package/dist/ws/index.js.map +4 -4
- package/package.json +16 -16
- package/dist/structures/permissions.d.ts +0 -73
package/dist/sharding/index.js
CHANGED
|
@@ -88,6 +88,22 @@ var GatewayOpcodes = {
|
|
|
88
88
|
Hello: 10,
|
|
89
89
|
HeartbeatAck: 11
|
|
90
90
|
};
|
|
91
|
+
var GatewayCloseCodes = {
|
|
92
|
+
UnknownError: 4000,
|
|
93
|
+
UnknownOpcode: 4001,
|
|
94
|
+
DecodeError: 4002,
|
|
95
|
+
NotAuthenticated: 4003,
|
|
96
|
+
AuthenticationFailed: 4004,
|
|
97
|
+
AlreadyAuthenticated: 4005,
|
|
98
|
+
InvalidSeq: 4007,
|
|
99
|
+
RateLimited: 4008,
|
|
100
|
+
SessionTimedOut: 4009,
|
|
101
|
+
InvalidShard: 4010,
|
|
102
|
+
ShardingRequired: 4011,
|
|
103
|
+
InvalidAPIVersion: 4012,
|
|
104
|
+
InvalidIntents: 4013,
|
|
105
|
+
DisallowedIntents: 4014
|
|
106
|
+
};
|
|
91
107
|
class GatewayError extends Error {
|
|
92
108
|
code;
|
|
93
109
|
constructor(message, code, options) {
|
|
@@ -121,6 +137,8 @@ class Gateway {
|
|
|
121
137
|
#rejectConnect;
|
|
122
138
|
#listeners = new Map;
|
|
123
139
|
#sendTimestamps = [];
|
|
140
|
+
ping = -1;
|
|
141
|
+
#decompressor;
|
|
124
142
|
constructor(options) {
|
|
125
143
|
if (!options.token?.trim())
|
|
126
144
|
throw new TypeError("A Gateway token is required.");
|
|
@@ -141,7 +159,12 @@ class Gateway {
|
|
|
141
159
|
browser: "Discord Android",
|
|
142
160
|
device: "Discord Android"
|
|
143
161
|
},
|
|
144
|
-
presence: {
|
|
162
|
+
presence: {
|
|
163
|
+
status: "online",
|
|
164
|
+
activities: [],
|
|
165
|
+
afk: false,
|
|
166
|
+
since: null
|
|
167
|
+
},
|
|
145
168
|
...options
|
|
146
169
|
};
|
|
147
170
|
if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
|
|
@@ -156,10 +179,13 @@ class Gateway {
|
|
|
156
179
|
return this.#connectPromise;
|
|
157
180
|
this.#closed = false;
|
|
158
181
|
this.#setState("CONNECT" /* Connect */);
|
|
182
|
+
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
|
|
183
|
+
if (this.#options.compress)
|
|
184
|
+
this.#initDecompressor();
|
|
159
185
|
this.#connectPromise = new Promise((resolve, reject) => {
|
|
160
186
|
this.#resolveConnect = resolve;
|
|
161
187
|
this.#rejectConnect = reject;
|
|
162
|
-
this.#open(
|
|
188
|
+
this.#open(connectURL);
|
|
163
189
|
});
|
|
164
190
|
return this.#connectPromise;
|
|
165
191
|
}
|
|
@@ -195,12 +221,15 @@ class Gateway {
|
|
|
195
221
|
send(payload) {
|
|
196
222
|
if (!payload || !Number.isInteger(payload.op))
|
|
197
223
|
throw new TypeError("Gateway payload must contain an integer opcode.");
|
|
224
|
+
return this.#dispatch(payload, false);
|
|
225
|
+
}
|
|
226
|
+
#dispatch(payload, privileged) {
|
|
198
227
|
if (this.#ws?.readyState !== WebSocket.OPEN)
|
|
199
228
|
return false;
|
|
200
229
|
const now = Date.now();
|
|
201
230
|
while (this.#sendTimestamps.length && now - this.#sendTimestamps[0] >= 60000)
|
|
202
231
|
this.#sendTimestamps.shift();
|
|
203
|
-
if (this.#sendTimestamps.length >= 115) {
|
|
232
|
+
if (!privileged && this.#sendTimestamps.length >= 115) {
|
|
204
233
|
this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
|
|
205
234
|
return false;
|
|
206
235
|
}
|
|
@@ -271,7 +300,11 @@ class Gateway {
|
|
|
271
300
|
ws.addEventListener("message", (event) => {
|
|
272
301
|
this.#lastMessageAt = Date.now();
|
|
273
302
|
this.#zombieReported = false;
|
|
274
|
-
this.#
|
|
303
|
+
if (this.#options.compress && event.data instanceof ArrayBuffer) {
|
|
304
|
+
this.#decompress(new Uint8Array(event.data)).then((text) => this.#message(ws, text)).catch((err) => this.#emitError(err));
|
|
305
|
+
} else {
|
|
306
|
+
this.#message(ws, String(event.data));
|
|
307
|
+
}
|
|
275
308
|
});
|
|
276
309
|
ws.addEventListener("close", (event) => this.#close(ws, event.code));
|
|
277
310
|
ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
|
|
@@ -279,7 +312,7 @@ class Gateway {
|
|
|
279
312
|
#startZombieDetection() {
|
|
280
313
|
if (this.#zombieTimer)
|
|
281
314
|
clearInterval(this.#zombieTimer);
|
|
282
|
-
const interval = Math.max(1, Math.min(
|
|
315
|
+
const interval = Math.max(1, Math.min(1000, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
|
|
283
316
|
this.#zombieTimer = setInterval(() => {
|
|
284
317
|
if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
|
|
285
318
|
return;
|
|
@@ -330,18 +363,24 @@ class Gateway {
|
|
|
330
363
|
break;
|
|
331
364
|
case GatewayOpcodes.HeartbeatAck:
|
|
332
365
|
this.#heartbeatACK = true;
|
|
366
|
+
this.ping = Date.now() - this.#heartbeatSentAt;
|
|
333
367
|
this.#clearHeartbeatAckTimer();
|
|
334
368
|
this.#emit("heartbeatAck", payload.d);
|
|
335
369
|
break;
|
|
336
370
|
case GatewayOpcodes.Reconnect:
|
|
337
371
|
ws.close(1001, "Server requested reconnect");
|
|
338
372
|
break;
|
|
339
|
-
case GatewayOpcodes.InvalidSession:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
373
|
+
case GatewayOpcodes.InvalidSession: {
|
|
374
|
+
const resumable = payload.d === true;
|
|
375
|
+
if (!resumable) {
|
|
376
|
+
this.#sessionId = undefined;
|
|
377
|
+
this.#sequence = null;
|
|
378
|
+
this.#resumeURL = undefined;
|
|
379
|
+
}
|
|
380
|
+
this.#emit("invalidSession", resumable);
|
|
381
|
+
ws.close(resumable ? GatewayCloseCodes.UnknownError : 1000, resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
344
382
|
break;
|
|
383
|
+
}
|
|
345
384
|
}
|
|
346
385
|
}
|
|
347
386
|
#handleDispatch(event, data) {
|
|
@@ -356,7 +395,14 @@ class Gateway {
|
|
|
356
395
|
this.#attempt = 0;
|
|
357
396
|
this.#setState("READY" /* Ready */);
|
|
358
397
|
this.#emit("ready", data);
|
|
398
|
+
} else if (event === "RESUMED") {
|
|
399
|
+
this.#attempt = 0;
|
|
400
|
+
this.#setState("READY" /* Ready */);
|
|
401
|
+
this.#emit("resumed", data);
|
|
359
402
|
}
|
|
403
|
+
this.#emit("RAW", { event: event ?? "UNKNOWN", data });
|
|
404
|
+
if (this.#closed)
|
|
405
|
+
return;
|
|
360
406
|
this.#emit(event ?? "dispatch", data);
|
|
361
407
|
}
|
|
362
408
|
#handleHello(data) {
|
|
@@ -430,12 +476,12 @@ class Gateway {
|
|
|
430
476
|
#sendHeartbeat() {
|
|
431
477
|
this.#heartbeatACK = false;
|
|
432
478
|
this.#heartbeatSentAt = Date.now();
|
|
433
|
-
if (!this
|
|
479
|
+
if (!this.#dispatch({
|
|
434
480
|
op: GatewayOpcodes.Heartbeat,
|
|
435
481
|
d: this.#sequence,
|
|
436
482
|
s: null,
|
|
437
483
|
t: null
|
|
438
|
-
})) {
|
|
484
|
+
}, true)) {
|
|
439
485
|
this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
440
486
|
return;
|
|
441
487
|
}
|
|
@@ -468,14 +514,23 @@ class Gateway {
|
|
|
468
514
|
if (action === "identify") {
|
|
469
515
|
this.#sequence = null;
|
|
470
516
|
this.#sessionId = undefined;
|
|
517
|
+
this.#resumeURL = undefined;
|
|
471
518
|
}
|
|
472
519
|
this.#setState("RECONNECT" /* Reconnect */);
|
|
473
520
|
this.#scheduleReconnect();
|
|
474
521
|
}
|
|
475
522
|
#closeAction(code) {
|
|
476
|
-
|
|
523
|
+
const fatal = [
|
|
524
|
+
GatewayCloseCodes.AuthenticationFailed,
|
|
525
|
+
GatewayCloseCodes.InvalidShard,
|
|
526
|
+
GatewayCloseCodes.ShardingRequired,
|
|
527
|
+
GatewayCloseCodes.InvalidAPIVersion,
|
|
528
|
+
GatewayCloseCodes.InvalidIntents,
|
|
529
|
+
GatewayCloseCodes.DisallowedIntents
|
|
530
|
+
];
|
|
531
|
+
if (fatal.includes(code))
|
|
477
532
|
return "stop";
|
|
478
|
-
if (
|
|
533
|
+
if (code === GatewayCloseCodes.InvalidSeq || code === GatewayCloseCodes.SessionTimedOut)
|
|
479
534
|
return "identify";
|
|
480
535
|
return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
|
|
481
536
|
}
|
|
@@ -488,7 +543,7 @@ class Gateway {
|
|
|
488
543
|
return;
|
|
489
544
|
}
|
|
490
545
|
const delay = Math.min(this.#options.reconnectMaxDelay, this.#options.reconnectBaseDelay * 2 ** this.#attempt++);
|
|
491
|
-
const jitter = Math.random() * Math.
|
|
546
|
+
const jitter = Math.random() * Math.max(1, delay * 0.25);
|
|
492
547
|
this.#reconnectTimer = setTimeout(() => {
|
|
493
548
|
this.#reconnectTimer = undefined;
|
|
494
549
|
this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
|
|
@@ -558,6 +613,37 @@ class Gateway {
|
|
|
558
613
|
this.state = next;
|
|
559
614
|
this.#emit("stateChange", { previous, next });
|
|
560
615
|
}
|
|
616
|
+
#initDecompressor() {
|
|
617
|
+
const ds = new DecompressionStream("deflate-raw");
|
|
618
|
+
this.#decompressor = {
|
|
619
|
+
writer: ds.writable.getWriter(),
|
|
620
|
+
reader: ds.readable.getReader()
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
async#decompress(chunk) {
|
|
624
|
+
if (!this.#decompressor)
|
|
625
|
+
this.#initDecompressor();
|
|
626
|
+
const { writer, reader } = this.#decompressor;
|
|
627
|
+
await writer.write(chunk);
|
|
628
|
+
const parts = [];
|
|
629
|
+
while (true) {
|
|
630
|
+
const { done, value } = await Promise.race([
|
|
631
|
+
reader.read(),
|
|
632
|
+
new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), 50))
|
|
633
|
+
]);
|
|
634
|
+
if (done || !value)
|
|
635
|
+
break;
|
|
636
|
+
parts.push(value);
|
|
637
|
+
}
|
|
638
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
639
|
+
const merged = new Uint8Array(total);
|
|
640
|
+
let offset = 0;
|
|
641
|
+
for (const p of parts) {
|
|
642
|
+
merged.set(p, offset);
|
|
643
|
+
offset += p.length;
|
|
644
|
+
}
|
|
645
|
+
return new TextDecoder().decode(merged);
|
|
646
|
+
}
|
|
561
647
|
}
|
|
562
648
|
|
|
563
649
|
// packages/sharding/src/bus.ts
|
|
@@ -633,8 +719,155 @@ class ShardBus {
|
|
|
633
719
|
}
|
|
634
720
|
}
|
|
635
721
|
}
|
|
722
|
+
// packages/sharding/src/cluster.ts
|
|
723
|
+
import { fork } from "child_process";
|
|
724
|
+
import { cpus } from "os";
|
|
725
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
726
|
+
|
|
727
|
+
class ClusterManager {
|
|
728
|
+
clusters = new Map;
|
|
729
|
+
shardCount = 0;
|
|
730
|
+
#options;
|
|
731
|
+
#auto;
|
|
732
|
+
#autoScaleTimer;
|
|
733
|
+
#spawned = false;
|
|
734
|
+
constructor(options) {
|
|
735
|
+
if (!options.token?.trim())
|
|
736
|
+
throw new TypeError("A Discord bot token is required.");
|
|
737
|
+
if (!options.script?.trim())
|
|
738
|
+
throw new TypeError("A script path is required for clustering.");
|
|
739
|
+
if (options.autoScaleInterval !== undefined && (!Number.isInteger(options.autoScaleInterval) || options.autoScaleInterval < 1000)) {
|
|
740
|
+
throw new RangeError("autoScaleInterval must be an integer of at least 1000 milliseconds.");
|
|
741
|
+
}
|
|
742
|
+
this.#auto = options.shardCount === "auto";
|
|
743
|
+
this.#options = { ...options };
|
|
744
|
+
}
|
|
745
|
+
async fetchRecommendedShardCount() {
|
|
746
|
+
const response = await fetch("https://discord.com/api/v10/gateway/bot", {
|
|
747
|
+
headers: {
|
|
748
|
+
Authorization: `Bot ${this.#options.token}`,
|
|
749
|
+
"User-Agent": "Lunibee/0.1.0"
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
if (!response.ok) {
|
|
753
|
+
throw new Error(`Gateway discovery failed with status ${response.status}`);
|
|
754
|
+
}
|
|
755
|
+
const data = await response.json();
|
|
756
|
+
if (typeof data.shards !== "number" || !Number.isInteger(data.shards) || data.shards < 1) {
|
|
757
|
+
throw new Error("Gateway discovery returned an invalid shard count.");
|
|
758
|
+
}
|
|
759
|
+
return data.shards;
|
|
760
|
+
}
|
|
761
|
+
async spawn() {
|
|
762
|
+
if (this.#spawned)
|
|
763
|
+
return;
|
|
764
|
+
if (typeof process?.versions?.bun === "string") {
|
|
765
|
+
throw new Error("ClusterManager requires the Node.js runtime; Bun does not support child_process.fork() for this clustering mode.");
|
|
766
|
+
}
|
|
767
|
+
this.#spawned = true;
|
|
768
|
+
const count = this.#options.shardCount === "auto" || this.#options.shardCount === undefined ? await this.fetchRecommendedShardCount() : this.#options.shardCount;
|
|
769
|
+
this.shardCount = count;
|
|
770
|
+
const clusterCount = this.#options.clusterCount ?? cpus().length;
|
|
771
|
+
const chunks = Array.from({ length: clusterCount }, () => []);
|
|
772
|
+
for (let i = 0;i < count; i++) {
|
|
773
|
+
chunks[i % clusterCount].push(i);
|
|
774
|
+
}
|
|
775
|
+
for (let i = 0;i < clusterCount; i++) {
|
|
776
|
+
if (chunks[i].length === 0)
|
|
777
|
+
continue;
|
|
778
|
+
const child = fork(this.#options.script, [], {
|
|
779
|
+
env: {
|
|
780
|
+
...process.env,
|
|
781
|
+
SHARD_LIST: chunks[i].join(","),
|
|
782
|
+
SHARD_COUNT: count.toString(),
|
|
783
|
+
CLUSTER_ID: i.toString()
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
this.clusters.set(i, {
|
|
787
|
+
id: i,
|
|
788
|
+
process: child,
|
|
789
|
+
shards: chunks[i]
|
|
790
|
+
});
|
|
791
|
+
await sleep(500);
|
|
792
|
+
}
|
|
793
|
+
if (this.#options.autoScaleInterval && this.#options.autoScaleInterval > 0) {
|
|
794
|
+
this.#autoScaleTimer = setInterval(() => {
|
|
795
|
+
this.checkAutoScale();
|
|
796
|
+
}, this.#options.autoScaleInterval);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async checkAutoScale() {
|
|
800
|
+
if (!this.#auto)
|
|
801
|
+
return;
|
|
802
|
+
try {
|
|
803
|
+
const recommended = await this.fetchRecommendedShardCount();
|
|
804
|
+
if (recommended !== this.shardCount) {
|
|
805
|
+
await this.respawn(recommended);
|
|
806
|
+
}
|
|
807
|
+
} catch (error) {
|
|
808
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
809
|
+
this.#options.onAutoScaleError?.(normalized);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async respawn(newShardCount) {
|
|
813
|
+
this.#options.shardCount = newShardCount;
|
|
814
|
+
await this.shutdownAll();
|
|
815
|
+
this.#spawned = false;
|
|
816
|
+
await this.spawn();
|
|
817
|
+
}
|
|
818
|
+
async shutdownAll(timeoutMs = this.#options.shutdownTimeout ?? 5000) {
|
|
819
|
+
if (this.#autoScaleTimer) {
|
|
820
|
+
clearInterval(this.#autoScaleTimer);
|
|
821
|
+
this.#autoScaleTimer = undefined;
|
|
822
|
+
}
|
|
823
|
+
await Promise.all([...this.clusters.values()].map((cluster) => this.#shutdownCluster(cluster, timeoutMs)));
|
|
824
|
+
this.clusters.clear();
|
|
825
|
+
this.#spawned = false;
|
|
826
|
+
}
|
|
827
|
+
async#shutdownCluster(cluster, timeoutMs) {
|
|
828
|
+
const child = cluster.process;
|
|
829
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
830
|
+
return;
|
|
831
|
+
await new Promise((resolve) => {
|
|
832
|
+
let timer;
|
|
833
|
+
const finish = () => {
|
|
834
|
+
if (timer)
|
|
835
|
+
clearTimeout(timer);
|
|
836
|
+
resolve();
|
|
837
|
+
};
|
|
838
|
+
child.once("exit", finish);
|
|
839
|
+
try {
|
|
840
|
+
child.kill("SIGTERM");
|
|
841
|
+
} catch {
|
|
842
|
+
child.off("exit", finish);
|
|
843
|
+
finish();
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
timer = setTimeout(() => {
|
|
847
|
+
try {
|
|
848
|
+
child.kill("SIGKILL");
|
|
849
|
+
} catch {}
|
|
850
|
+
}, timeoutMs);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
killAll() {
|
|
854
|
+
if (this.#autoScaleTimer) {
|
|
855
|
+
clearInterval(this.#autoScaleTimer);
|
|
856
|
+
this.#autoScaleTimer = undefined;
|
|
857
|
+
}
|
|
858
|
+
for (const cluster of this.clusters.values()) {
|
|
859
|
+
try {
|
|
860
|
+
cluster.process.kill();
|
|
861
|
+
} catch {}
|
|
862
|
+
}
|
|
863
|
+
this.clusters.clear();
|
|
864
|
+
this.#spawned = false;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
636
867
|
|
|
637
868
|
// packages/sharding/src/index.ts
|
|
869
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
870
|
+
|
|
638
871
|
class ShardManager {
|
|
639
872
|
shards = new Map;
|
|
640
873
|
get shardCount() {
|
|
@@ -643,6 +876,8 @@ class ShardManager {
|
|
|
643
876
|
#options;
|
|
644
877
|
#resolved = false;
|
|
645
878
|
#destroyed = false;
|
|
879
|
+
#auto;
|
|
880
|
+
#autoScaleTimer;
|
|
646
881
|
constructor(options) {
|
|
647
882
|
if (!options.token?.trim())
|
|
648
883
|
throw new TypeError("A Discord bot token is required.");
|
|
@@ -651,6 +886,9 @@ class ShardManager {
|
|
|
651
886
|
const count = options.shardCount === "auto" || options.shardCount === undefined ? 1 : options.shardCount;
|
|
652
887
|
if (!Number.isInteger(count) || count < 1)
|
|
653
888
|
throw new RangeError("Shard count must be a positive integer.");
|
|
889
|
+
if (options.autoScaleInterval !== undefined && (!Number.isInteger(options.autoScaleInterval) || options.autoScaleInterval < 1000))
|
|
890
|
+
throw new RangeError("autoScaleInterval must be an integer of at least 1000 milliseconds.");
|
|
891
|
+
this.#auto = options.shardCount === "auto";
|
|
654
892
|
this.#options = { ...options };
|
|
655
893
|
if (options.shardCount !== "auto")
|
|
656
894
|
this.#initialize(count);
|
|
@@ -670,24 +908,56 @@ class ShardManager {
|
|
|
670
908
|
return data.shards;
|
|
671
909
|
}
|
|
672
910
|
async connect() {
|
|
911
|
+
if (this.#autoScaleTimer) {
|
|
912
|
+
clearInterval(this.#autoScaleTimer);
|
|
913
|
+
this.#autoScaleTimer = undefined;
|
|
914
|
+
}
|
|
673
915
|
await this.#ensureInitialized();
|
|
674
916
|
for (const [id, shard] of this.shards) {
|
|
675
917
|
try {
|
|
676
918
|
await shard.connect();
|
|
677
919
|
} catch (error) {
|
|
678
920
|
this.destroy();
|
|
679
|
-
throw new Error(`Failed to connect shard ${id}.`, {
|
|
921
|
+
throw new Error(`Failed to connect shard ${id}.`, {
|
|
922
|
+
cause: error
|
|
923
|
+
});
|
|
680
924
|
}
|
|
681
925
|
if (id + 1 < this.shardCount && this.#options.spawnDelay && this.#options.spawnDelay > 0)
|
|
682
|
-
await
|
|
926
|
+
await sleep2(this.#options.spawnDelay);
|
|
927
|
+
}
|
|
928
|
+
if (this.#options.autoScaleInterval && !this.#autoScaleTimer) {
|
|
929
|
+
this.#autoScaleTimer = setInterval(() => {
|
|
930
|
+
this.checkAutoScale();
|
|
931
|
+
}, this.#options.autoScaleInterval);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
async checkAutoScale() {
|
|
935
|
+
if (!this.#auto)
|
|
936
|
+
return;
|
|
937
|
+
try {
|
|
938
|
+
const recommended = await this.fetchRecommendedShardCount();
|
|
939
|
+
if (recommended !== this.shardCount) {
|
|
940
|
+
await this.reshard(recommended);
|
|
941
|
+
}
|
|
942
|
+
} catch (error) {
|
|
943
|
+
this.#options.onAutoScaleError?.(error);
|
|
683
944
|
}
|
|
684
945
|
}
|
|
946
|
+
async reshard(count) {
|
|
947
|
+
this.#options.shardCount = count;
|
|
948
|
+
this.destroy();
|
|
949
|
+
await this.connect();
|
|
950
|
+
}
|
|
685
951
|
destroy() {
|
|
686
952
|
for (const shard of this.shards.values())
|
|
687
953
|
shard.close();
|
|
688
954
|
this.shards.clear();
|
|
689
955
|
this.#resolved = false;
|
|
690
956
|
this.#destroyed = true;
|
|
957
|
+
if (this.#autoScaleTimer) {
|
|
958
|
+
clearInterval(this.#autoScaleTimer);
|
|
959
|
+
this.#autoScaleTimer = undefined;
|
|
960
|
+
}
|
|
691
961
|
}
|
|
692
962
|
getShardIdForGuild(guildId) {
|
|
693
963
|
const id = BigInt(guildId);
|
|
@@ -725,8 +995,9 @@ class ShardManager {
|
|
|
725
995
|
}
|
|
726
996
|
}
|
|
727
997
|
export {
|
|
998
|
+
ClusterManager,
|
|
728
999
|
ShardBus,
|
|
729
1000
|
ShardManager
|
|
730
1001
|
};
|
|
731
1002
|
|
|
732
|
-
//# debugId=
|
|
1003
|
+
//# debugId=F2AF359ADCA0121464756E2164756E21
|