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.
- package/LICENSE +21 -21
- package/README.md +223 -223
- package/dist/builders/commands.d.ts +6 -4
- package/dist/builders/components.d.ts +18 -0
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +49 -13
- package/dist/builders/index.js.map +9 -8
- package/dist/collection/index.js +2 -1
- package/dist/collection/index.js.map +4 -4
- package/dist/core/events.d.ts +86 -2
- package/dist/core/index.d.ts +10 -87
- package/dist/core/index.js +1752 -725
- package/dist/core/index.js.map +44 -24
- package/dist/formatters/index.js.map +1 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2103 -800
- package/dist/index.js.map +63 -43
- package/dist/managers/guild-resources.d.ts +109 -0
- package/dist/managers/guild.d.ts +5 -0
- package/dist/managers/index.d.ts +7 -1
- package/dist/managers/index.js +330 -12
- package/dist/managers/index.js.map +25 -21
- package/dist/rest/decoder.d.ts +23 -0
- package/dist/rest/errors.d.ts +20 -0
- package/dist/rest/index.d.ts +21 -17
- package/dist/rest/index.js +479 -263
- package/dist/rest/index.js.map +14 -8
- package/dist/rest/limiter.d.ts +42 -0
- package/dist/rest/redis.d.ts +18 -1
- package/dist/rest/route.d.ts +31 -0
- package/dist/rest/routes.d.ts +2 -0
- package/dist/rest/scheduler.d.ts +42 -0
- package/dist/rest/store.d.ts +29 -0
- package/dist/rest/transport.d.ts +40 -0
- package/dist/sharding/bus.d.ts +31 -0
- package/dist/sharding/cluster.d.ts +10 -0
- package/dist/sharding/index.d.ts +10 -2
- package/dist/sharding/index.js +1011 -394
- package/dist/sharding/index.js.map +18 -8
- package/dist/structures/base.d.ts +8 -0
- package/dist/structures/channels.d.ts +56 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +211 -271
- package/dist/structures/index.js.map +17 -14
- package/dist/structures/interactions.d.ts +12 -52
- package/dist/structures/options.d.ts +55 -0
- package/dist/structures/resources.d.ts +3 -3
- package/dist/types/gateway-events.d.ts +132 -0
- package/dist/types/gateway.d.ts +181 -0
- package/dist/types/index.d.ts +2 -263
- package/dist/types/index.js +44 -2
- package/dist/types/index.js.map +5 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/voice/index.d.ts +29 -1
- package/dist/voice/index.js +59 -7
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/close-codes.d.ts +23 -0
- package/dist/ws/decoder.d.ts +58 -0
- package/dist/ws/heartbeat.d.ts +87 -0
- package/dist/ws/index.d.ts +18 -65
- package/dist/ws/index.js +903 -385
- package/dist/ws/index.js.map +15 -5
- package/dist/ws/opcodes.d.ts +14 -0
- package/dist/ws/protocol.d.ts +110 -0
- package/dist/ws/reconnect.d.ts +108 -0
- package/dist/ws/send-budget.d.ts +17 -0
- package/dist/ws/session.d.ts +100 -0
- package/dist/ws/state.d.ts +27 -0
- package/dist/ws/transport.d.ts +80 -0
- package/package.json +21 -20
package/dist/core/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/core/src/permissions.ts
|
|
3
3
|
var PermissionOverwriteType;
|
|
4
|
-
((
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
((
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
551
|
+
const onCollect = (item) => {
|
|
552
|
+
this.off("end", onEnd);
|
|
552
553
|
resolve(item);
|
|
553
|
-
}
|
|
554
|
-
|
|
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/
|
|
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.
|
|
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
|
-
|
|
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
|
-
#
|
|
1128
|
-
#
|
|
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.#
|
|
1135
|
-
|
|
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
|
-
|
|
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
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
let
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
if (options.
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
if (
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
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
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
-
|
|
1317
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
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/
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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/
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
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
|
-
#
|
|
2656
|
-
#
|
|
2657
|
-
#
|
|
2658
|
-
#
|
|
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
|
-
#
|
|
2670
|
-
#reconnectTimer;
|
|
2671
|
-
#connectPromise;
|
|
2672
|
-
#resolveConnect;
|
|
2673
|
-
#rejectConnect;
|
|
3873
|
+
#reconnect;
|
|
2674
3874
|
#listeners = new Map;
|
|
2675
|
-
#
|
|
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 =
|
|
3950
|
+
connect(url = DEFAULT_GATEWAY_URL) {
|
|
2712
3951
|
if (this.state === "CLOSED" /* Closed */)
|
|
2713
3952
|
throw new GatewayError("Gateway has been permanently closed.");
|
|
2714
|
-
|
|
2715
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.#
|
|
3993
|
+
if (!this.#transport.connected)
|
|
2764
3994
|
return false;
|
|
2765
3995
|
const now = Date.now();
|
|
2766
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
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
|
-
#
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
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 (
|
|
2887
|
-
this.#sequence
|
|
2888
|
-
|
|
2889
|
-
|
|
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(
|
|
2892
|
-
|
|
2893
|
-
case
|
|
2894
|
-
this.#handleHello(
|
|
2895
|
-
|
|
2896
|
-
case
|
|
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
|
-
|
|
2900
|
-
case
|
|
2901
|
-
this.#
|
|
2902
|
-
this.ping =
|
|
2903
|
-
this.#
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
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
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
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.#
|
|
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.#
|
|
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(
|
|
2945
|
-
|
|
2946
|
-
|
|
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
|
-
|
|
4127
|
+
const intent = this.#session.handshake();
|
|
4128
|
+
if (intent.type === "resume") {
|
|
2958
4129
|
this.#setState("RESUME" /* Resume */);
|
|
2959
|
-
this.
|
|
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
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
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.#
|
|
4145
|
+
const action = this.#reconnect.classifyClose(code, this.#session.canResume);
|
|
3043
4146
|
this.#emit("close", { code, action });
|
|
3044
|
-
if (this.#closed || !this.#
|
|
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.#
|
|
3047
|
-
this.#
|
|
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.#
|
|
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
|
|
4161
|
+
if (this.#closed)
|
|
3075
4162
|
return;
|
|
3076
|
-
|
|
3077
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3115
|
-
|
|
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,
|
|
3299
|
-
editMessage: (channelId, messageId,
|
|
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,
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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=
|
|
4589
|
+
//# debugId=B9E28396E97D857564756E2164756E21
|