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/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,45 @@ 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
|
+
|
|
679
|
+
// packages/types/src/index.ts
|
|
635
680
|
var MessageFlags = {
|
|
636
681
|
Crossposted: 1,
|
|
637
682
|
IsCrosspost: 2,
|
|
@@ -734,6 +779,7 @@ class Cache {
|
|
|
734
779
|
if (!Number.isFinite(interval) || interval < 1)
|
|
735
780
|
throw new RangeError("Cache sweepInterval must be positive.");
|
|
736
781
|
this.#timer = setInterval(() => this.sweep(), interval);
|
|
782
|
+
this.#timer.unref?.();
|
|
737
783
|
}
|
|
738
784
|
}
|
|
739
785
|
get size() {
|
|
@@ -1097,7 +1143,7 @@ class ResourceManager extends Manager {
|
|
|
1097
1143
|
// packages/rest/package.json
|
|
1098
1144
|
var package_default = {
|
|
1099
1145
|
name: "@lunibee/rest",
|
|
1100
|
-
version: "0.1.
|
|
1146
|
+
version: "0.1.8",
|
|
1101
1147
|
type: "module",
|
|
1102
1148
|
module: "./src/index.ts",
|
|
1103
1149
|
types: "./src/index.ts",
|
|
@@ -1122,6 +1168,17 @@ class MemoryRateLimitStore {
|
|
|
1122
1168
|
getBucket(key) {
|
|
1123
1169
|
return this.#buckets.get(key);
|
|
1124
1170
|
}
|
|
1171
|
+
reserve(key) {
|
|
1172
|
+
const state = this.#buckets.get(key);
|
|
1173
|
+
const now = Date.now();
|
|
1174
|
+
if (!state || state.resetAt <= now)
|
|
1175
|
+
return { granted: true };
|
|
1176
|
+
if (state.remaining > 0) {
|
|
1177
|
+
state.remaining -= 1;
|
|
1178
|
+
return { granted: true };
|
|
1179
|
+
}
|
|
1180
|
+
return { granted: false, retryAfterMs: state.resetAt - now };
|
|
1181
|
+
}
|
|
1125
1182
|
updateBucket(key, state) {
|
|
1126
1183
|
this.#buckets.set(key, state);
|
|
1127
1184
|
const now = Date.now();
|
|
@@ -1145,6 +1202,301 @@ class MemoryRateLimitStore {
|
|
|
1145
1202
|
}
|
|
1146
1203
|
}
|
|
1147
1204
|
|
|
1205
|
+
// packages/rest/src/errors.ts
|
|
1206
|
+
class RESTError extends Error {
|
|
1207
|
+
status;
|
|
1208
|
+
code;
|
|
1209
|
+
errors;
|
|
1210
|
+
method;
|
|
1211
|
+
path;
|
|
1212
|
+
constructor(message, status, code, errors, options = {}) {
|
|
1213
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
1214
|
+
this.name = "RESTError";
|
|
1215
|
+
this.status = status;
|
|
1216
|
+
this.code = code;
|
|
1217
|
+
this.errors = errors;
|
|
1218
|
+
this.method = options.method;
|
|
1219
|
+
this.path = options.path;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
function abortError(path, reason) {
|
|
1223
|
+
return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
|
|
1224
|
+
}
|
|
1225
|
+
async function sleep(delay, signal, path) {
|
|
1226
|
+
if (delay <= 0)
|
|
1227
|
+
return;
|
|
1228
|
+
await new Promise((resolve, reject) => {
|
|
1229
|
+
const timer = setTimeout(resolve, delay);
|
|
1230
|
+
const abort = () => {
|
|
1231
|
+
clearTimeout(timer);
|
|
1232
|
+
reject(abortError(path, signal?.reason));
|
|
1233
|
+
};
|
|
1234
|
+
if (signal?.aborted)
|
|
1235
|
+
return abort();
|
|
1236
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
async function abortable(promise, signal, path) {
|
|
1240
|
+
if (!signal)
|
|
1241
|
+
return promise;
|
|
1242
|
+
if (signal.aborted)
|
|
1243
|
+
throw abortError(path, signal.reason);
|
|
1244
|
+
return Promise.race([
|
|
1245
|
+
promise,
|
|
1246
|
+
new Promise((_, reject) => signal.addEventListener("abort", () => reject(abortError(path, signal.reason)), { once: true }))
|
|
1247
|
+
]);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// packages/rest/src/route.ts
|
|
1251
|
+
function normalizeRoutePath(path) {
|
|
1252
|
+
const queryIndex = path.indexOf("?");
|
|
1253
|
+
const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
|
|
1254
|
+
return routePath.replace(/\/\d+(?=\/|$)/g, "/:id");
|
|
1255
|
+
}
|
|
1256
|
+
function majorParameter(path) {
|
|
1257
|
+
const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/.exec(path);
|
|
1258
|
+
if (!match)
|
|
1259
|
+
return "@none";
|
|
1260
|
+
if (match[1] === "webhooks" && match[3] !== undefined)
|
|
1261
|
+
return `${match[2]}:${match[3]}`;
|
|
1262
|
+
return match[2];
|
|
1263
|
+
}
|
|
1264
|
+
function scopeBucket(hashOrRoute, major) {
|
|
1265
|
+
return `${hashOrRoute}|${major}`;
|
|
1266
|
+
}
|
|
1267
|
+
function createRouteKey(method, path) {
|
|
1268
|
+
const normalizedMethod = method.toUpperCase();
|
|
1269
|
+
return {
|
|
1270
|
+
method: normalizedMethod,
|
|
1271
|
+
path,
|
|
1272
|
+
route: `${normalizedMethod}:${normalizeRoutePath(path)}`,
|
|
1273
|
+
major: majorParameter(path)
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function serializeQuery(query) {
|
|
1277
|
+
if (query === undefined)
|
|
1278
|
+
return "";
|
|
1279
|
+
if (typeof query === "string")
|
|
1280
|
+
return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
|
|
1281
|
+
const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
|
|
1282
|
+
const serialized = params.toString();
|
|
1283
|
+
return serialized.length === 0 ? "" : `?${serialized}`;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// packages/rest/src/scheduler.ts
|
|
1287
|
+
class RequestScheduler {
|
|
1288
|
+
static MAX_REMAPS = 2;
|
|
1289
|
+
#queues = new Map;
|
|
1290
|
+
get size() {
|
|
1291
|
+
return this.#queues.size;
|
|
1292
|
+
}
|
|
1293
|
+
async runResolved(resolveKey, signal, path, task) {
|
|
1294
|
+
let key = await resolveKey();
|
|
1295
|
+
for (let hop = 0;hop < RequestScheduler.MAX_REMAPS; hop++) {
|
|
1296
|
+
const outcome = await this.run(key, signal, path, async (release) => {
|
|
1297
|
+
const current = await resolveKey();
|
|
1298
|
+
if (current !== key)
|
|
1299
|
+
return { done: false, key: current };
|
|
1300
|
+
return { done: true, value: await task(key, release) };
|
|
1301
|
+
});
|
|
1302
|
+
if (outcome.done)
|
|
1303
|
+
return outcome.value;
|
|
1304
|
+
key = outcome.key;
|
|
1305
|
+
}
|
|
1306
|
+
return this.run(key, signal, path, (release) => task(key, release));
|
|
1307
|
+
}
|
|
1308
|
+
async run(key, signal, path, task) {
|
|
1309
|
+
const previous = this.#queues.get(key) ?? Promise.resolve();
|
|
1310
|
+
let open;
|
|
1311
|
+
const gate = new Promise((resolve) => {
|
|
1312
|
+
open = resolve;
|
|
1313
|
+
});
|
|
1314
|
+
this.#queues.set(key, gate);
|
|
1315
|
+
let released = false;
|
|
1316
|
+
const release = () => {
|
|
1317
|
+
if (released)
|
|
1318
|
+
return;
|
|
1319
|
+
released = true;
|
|
1320
|
+
open();
|
|
1321
|
+
if (this.#queues.get(key) === gate)
|
|
1322
|
+
this.#queues.delete(key);
|
|
1323
|
+
};
|
|
1324
|
+
try {
|
|
1325
|
+
await abortable(previous, signal, path);
|
|
1326
|
+
return await task(release);
|
|
1327
|
+
} finally {
|
|
1328
|
+
release();
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// packages/rest/src/limiter.ts
|
|
1334
|
+
class RateLimiter {
|
|
1335
|
+
#store;
|
|
1336
|
+
#concurrent;
|
|
1337
|
+
constructor(store, options = {}) {
|
|
1338
|
+
this.#store = store;
|
|
1339
|
+
this.#concurrent = options.concurrent ?? false;
|
|
1340
|
+
}
|
|
1341
|
+
get store() {
|
|
1342
|
+
return this.#store;
|
|
1343
|
+
}
|
|
1344
|
+
async resolveBucketKey(route, major) {
|
|
1345
|
+
return scopeBucket(await this.#store.getBucketHash(route) ?? route, major);
|
|
1346
|
+
}
|
|
1347
|
+
get reserves() {
|
|
1348
|
+
return typeof this.#store.reserve === "function";
|
|
1349
|
+
}
|
|
1350
|
+
async acquire(bucketKey, signal, path) {
|
|
1351
|
+
await this.#waitGlobal(signal, path);
|
|
1352
|
+
if (!this.#store.reserve) {
|
|
1353
|
+
const bucket = await this.#store.getBucket(bucketKey);
|
|
1354
|
+
if (bucket) {
|
|
1355
|
+
const delay = bucket.resetAt - Date.now();
|
|
1356
|
+
if (bucket.remaining <= 0 && delay > 0)
|
|
1357
|
+
await sleep(delay, signal, path);
|
|
1358
|
+
}
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
for (let attempt = 0;attempt < 8; attempt++) {
|
|
1362
|
+
const reservation = await this.#store.reserve(bucketKey);
|
|
1363
|
+
if (reservation.granted)
|
|
1364
|
+
return;
|
|
1365
|
+
await sleep(Math.max(1, reservation.retryAfterMs), signal, path);
|
|
1366
|
+
await this.#waitGlobal(signal, path);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
async#waitGlobal(signal, path) {
|
|
1370
|
+
const globalDelay = await this.#store.getGlobalReset() - Date.now();
|
|
1371
|
+
if (globalDelay > 0)
|
|
1372
|
+
await sleep(globalDelay, signal, path);
|
|
1373
|
+
}
|
|
1374
|
+
async noteGlobalReset(resetAt) {
|
|
1375
|
+
const current = await this.#store.getGlobalReset();
|
|
1376
|
+
await this.#store.setGlobalReset(Math.max(current, resetAt));
|
|
1377
|
+
}
|
|
1378
|
+
async applyResponse(response, bucketKey, route, major) {
|
|
1379
|
+
let currentKey = bucketKey;
|
|
1380
|
+
const serverBucket = response.headers.get("X-RateLimit-Bucket");
|
|
1381
|
+
if (serverBucket) {
|
|
1382
|
+
await this.#store.setBucketHash(route, serverBucket);
|
|
1383
|
+
currentKey = scopeBucket(serverBucket, major);
|
|
1384
|
+
}
|
|
1385
|
+
const stored = await this.#store.getBucket(currentKey);
|
|
1386
|
+
const bucket = stored ? { ...stored } : { remaining: 1, resetAt: 0 };
|
|
1387
|
+
const windowActive = this.#concurrent && !!stored && stored.resetAt > Date.now();
|
|
1388
|
+
const remaining = Number(response.headers.get("X-RateLimit-Remaining"));
|
|
1389
|
+
const resetAfter = Number(response.headers.get("X-RateLimit-Reset-After"));
|
|
1390
|
+
const reset = Number(response.headers.get("X-RateLimit-Reset"));
|
|
1391
|
+
if (Number.isFinite(remaining))
|
|
1392
|
+
bucket.remaining = Math.max(0, remaining);
|
|
1393
|
+
if (Number.isFinite(resetAfter))
|
|
1394
|
+
bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;
|
|
1395
|
+
else if (Number.isFinite(reset))
|
|
1396
|
+
bucket.resetAt = reset * 1000;
|
|
1397
|
+
if (windowActive && stored) {
|
|
1398
|
+
bucket.remaining = Math.min(bucket.remaining, stored.remaining);
|
|
1399
|
+
bucket.resetAt = Math.max(bucket.resetAt, stored.resetAt);
|
|
1400
|
+
}
|
|
1401
|
+
if (response.status === 429) {
|
|
1402
|
+
const retryAfter = Number(response.headers.get("Retry-After"));
|
|
1403
|
+
if (Number.isFinite(retryAfter))
|
|
1404
|
+
bucket.resetAt = Math.max(bucket.resetAt, Date.now() + retryAfter * 1000);
|
|
1405
|
+
}
|
|
1406
|
+
await this.#store.updateBucket(currentKey, bucket);
|
|
1407
|
+
if (response.headers.get("X-RateLimit-Global") === "true") {
|
|
1408
|
+
const retryAfter = Number(response.headers.get("Retry-After"));
|
|
1409
|
+
if (Number.isFinite(retryAfter))
|
|
1410
|
+
await this.noteGlobalReset(Date.now() + retryAfter * 1000);
|
|
1411
|
+
}
|
|
1412
|
+
return currentKey;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// packages/rest/src/transport.ts
|
|
1417
|
+
class TransportError extends Error {
|
|
1418
|
+
timedOut;
|
|
1419
|
+
constructor(message, timedOut, cause) {
|
|
1420
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
1421
|
+
this.name = "TransportError";
|
|
1422
|
+
this.timedOut = timedOut;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
class HttpTransport {
|
|
1427
|
+
#baseURL;
|
|
1428
|
+
#timeout;
|
|
1429
|
+
#fetch;
|
|
1430
|
+
constructor(options = {}) {
|
|
1431
|
+
this.#baseURL = (options.baseURL ?? "https://discord.com/api/v10").replace(/\/$/, "");
|
|
1432
|
+
this.#timeout = Math.max(1, options.timeout ?? 15000);
|
|
1433
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
1434
|
+
}
|
|
1435
|
+
get baseURL() {
|
|
1436
|
+
return this.#baseURL;
|
|
1437
|
+
}
|
|
1438
|
+
get timeout() {
|
|
1439
|
+
return this.#timeout;
|
|
1440
|
+
}
|
|
1441
|
+
async send(request) {
|
|
1442
|
+
if (request.signal?.aborted)
|
|
1443
|
+
throw abortError(request.path, request.signal.reason);
|
|
1444
|
+
const controller = new AbortController;
|
|
1445
|
+
const onAbort = () => controller.abort(request.signal?.reason);
|
|
1446
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1447
|
+
const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
|
|
1448
|
+
try {
|
|
1449
|
+
return await this.#fetch(`${this.#baseURL}${request.path}`, {
|
|
1450
|
+
method: request.method,
|
|
1451
|
+
headers: request.headers,
|
|
1452
|
+
body: request.body,
|
|
1453
|
+
signal: controller.signal
|
|
1454
|
+
});
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
if (error instanceof RESTError)
|
|
1457
|
+
throw error;
|
|
1458
|
+
const timedOut = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
1459
|
+
throw new TransportError(timedOut ? `Discord REST request timed out after ${this.#timeout}ms` : "Discord REST request failed", timedOut, error);
|
|
1460
|
+
} finally {
|
|
1461
|
+
clearTimeout(timer);
|
|
1462
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// packages/rest/src/decoder.ts
|
|
1468
|
+
class ResponseDecoder {
|
|
1469
|
+
async read(response) {
|
|
1470
|
+
if (response.status === 204)
|
|
1471
|
+
return;
|
|
1472
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1473
|
+
if (contentType.includes("application/json"))
|
|
1474
|
+
return response.json().catch(() => {
|
|
1475
|
+
return;
|
|
1476
|
+
});
|
|
1477
|
+
return response.text().catch(() => {
|
|
1478
|
+
return;
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
errorData(payload) {
|
|
1482
|
+
if (!payload || typeof payload !== "object")
|
|
1483
|
+
return {};
|
|
1484
|
+
const data = payload;
|
|
1485
|
+
return {
|
|
1486
|
+
message: typeof data.message === "string" ? data.message : undefined,
|
|
1487
|
+
code: typeof data.code === "number" ? data.code : undefined,
|
|
1488
|
+
retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
|
|
1489
|
+
global: data.global === true
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
retryAfter(response, data) {
|
|
1493
|
+
const raw = response.headers.get("Retry-After");
|
|
1494
|
+
const header = raw === null ? Number.NaN : Number(raw);
|
|
1495
|
+
const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
|
|
1496
|
+
return Math.max(0, retryAfter);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1148
1500
|
// packages/rest/src/routes.ts
|
|
1149
1501
|
var Routes = {
|
|
1150
1502
|
user: () => "/users/@me",
|
|
@@ -1186,6 +1538,8 @@ var Routes = {
|
|
|
1186
1538
|
threadMembers: (threadId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members`,
|
|
1187
1539
|
guildScheduledEvents: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events`,
|
|
1188
1540
|
guildScheduledEvent: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}`,
|
|
1541
|
+
guildScheduledEventUsers: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}/users`,
|
|
1542
|
+
channelPermission: (channelId, overwriteId) => `/channels/${snowflake(channelId, "Channel ID")}/permissions/${snowflake(overwriteId, "Overwrite ID")}`,
|
|
1189
1543
|
guildAutoModerationRules: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules`,
|
|
1190
1544
|
guildAutoModerationRule: (guildId, ruleId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules/${snowflake(ruleId, "Rule ID")}`,
|
|
1191
1545
|
voiceRegions: () => "/voice/regions",
|
|
@@ -1209,15 +1563,36 @@ function snowflake(value, field) {
|
|
|
1209
1563
|
return value;
|
|
1210
1564
|
}
|
|
1211
1565
|
// packages/rest/src/redis.ts
|
|
1566
|
+
var RESERVE_SCRIPT = `
|
|
1567
|
+
local raw = redis.call('GET', KEYS[1])
|
|
1568
|
+
if not raw then return {1, 0} end
|
|
1569
|
+
local ok, state = pcall(cjson.decode, raw)
|
|
1570
|
+
if not ok or type(state) ~= 'table' or type(state.resetAt) ~= 'number' then
|
|
1571
|
+
return {1, 0}
|
|
1572
|
+
end
|
|
1573
|
+
local now = tonumber(ARGV[1])
|
|
1574
|
+
local wait = state.resetAt - now
|
|
1575
|
+
if wait <= 0 then return {1, 0} end
|
|
1576
|
+
if type(state.remaining) == 'number' and state.remaining > 0 then
|
|
1577
|
+
state.remaining = state.remaining - 1
|
|
1578
|
+
redis.call('SET', KEYS[1], cjson.encode(state), 'PX', math.ceil(wait))
|
|
1579
|
+
return {1, 0}
|
|
1580
|
+
end
|
|
1581
|
+
return {0, math.ceil(wait)}
|
|
1582
|
+
`;
|
|
1583
|
+
|
|
1212
1584
|
class RedisRateLimitStore {
|
|
1213
1585
|
#client;
|
|
1214
1586
|
#prefix;
|
|
1215
1587
|
#onErrorCallback;
|
|
1588
|
+
#local = new MemoryRateLimitStore;
|
|
1216
1589
|
#lastError;
|
|
1217
1590
|
constructor(options) {
|
|
1218
1591
|
this.#client = options.client;
|
|
1219
1592
|
this.#prefix = options.prefix ?? "lunibee:rest:";
|
|
1220
1593
|
this.#onErrorCallback = options.onError;
|
|
1594
|
+
if (typeof options.client.eval === "function")
|
|
1595
|
+
this.reserve = (key) => this.#reserve(key);
|
|
1221
1596
|
}
|
|
1222
1597
|
get lastError() {
|
|
1223
1598
|
return this.#lastError;
|
|
@@ -1242,10 +1617,11 @@ class RedisRateLimitStore {
|
|
|
1242
1617
|
return hash ?? undefined;
|
|
1243
1618
|
} catch (error) {
|
|
1244
1619
|
this.#onError("getBucketHash", error);
|
|
1245
|
-
return;
|
|
1620
|
+
return this.#local.getBucketHash(route);
|
|
1246
1621
|
}
|
|
1247
1622
|
}
|
|
1248
1623
|
async setBucketHash(route, hash) {
|
|
1624
|
+
this.#local.setBucketHash(route, hash);
|
|
1249
1625
|
try {
|
|
1250
1626
|
await this.#client.set(`${this.#prefix}route:${route}`, hash, "EX", 604800);
|
|
1251
1627
|
this.#markHealthy();
|
|
@@ -1262,10 +1638,11 @@ class RedisRateLimitStore {
|
|
|
1262
1638
|
return JSON.parse(data);
|
|
1263
1639
|
} catch (error) {
|
|
1264
1640
|
this.#onError("getBucket", error);
|
|
1265
|
-
return;
|
|
1641
|
+
return this.#local.getBucket(key);
|
|
1266
1642
|
}
|
|
1267
1643
|
}
|
|
1268
1644
|
async updateBucket(key, state) {
|
|
1645
|
+
this.#local.updateBucket(key, state);
|
|
1269
1646
|
const bucketKey = `${this.#prefix}bucket:${key}`;
|
|
1270
1647
|
const ttl = Math.ceil((state.resetAt - Date.now()) / 1000);
|
|
1271
1648
|
try {
|
|
@@ -1280,6 +1657,23 @@ class RedisRateLimitStore {
|
|
|
1280
1657
|
this.#onError("updateBucket", error);
|
|
1281
1658
|
}
|
|
1282
1659
|
}
|
|
1660
|
+
get supportsReservation() {
|
|
1661
|
+
return typeof this.#client.eval === "function";
|
|
1662
|
+
}
|
|
1663
|
+
reserve;
|
|
1664
|
+
async#reserve(key) {
|
|
1665
|
+
try {
|
|
1666
|
+
const result = await this.#client.eval(RESERVE_SCRIPT, 1, `${this.#prefix}bucket:${key}`, Date.now());
|
|
1667
|
+
this.#markHealthy();
|
|
1668
|
+
if (!Array.isArray(result))
|
|
1669
|
+
return { granted: true };
|
|
1670
|
+
const [granted, retryAfterMs] = result;
|
|
1671
|
+
return Number(granted) === 1 ? { granted: true } : { granted: false, retryAfterMs: Number(retryAfterMs) || 0 };
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
this.#onError("reserve", error);
|
|
1674
|
+
return this.#local.reserve(key);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1283
1677
|
async getGlobalReset() {
|
|
1284
1678
|
try {
|
|
1285
1679
|
const resetAt = await this.#client.get(`${this.#prefix}global`);
|
|
@@ -1287,10 +1681,11 @@ class RedisRateLimitStore {
|
|
|
1287
1681
|
return resetAt ? Number(resetAt) : 0;
|
|
1288
1682
|
} catch (error) {
|
|
1289
1683
|
this.#onError("getGlobalReset", error);
|
|
1290
|
-
return
|
|
1684
|
+
return this.#local.getGlobalReset();
|
|
1291
1685
|
}
|
|
1292
1686
|
}
|
|
1293
1687
|
async setGlobalReset(resetAt) {
|
|
1688
|
+
this.#local.setGlobalReset(resetAt);
|
|
1294
1689
|
const globalKey = `${this.#prefix}global`;
|
|
1295
1690
|
const ttl = Math.ceil((resetAt - Date.now()) / 1000);
|
|
1296
1691
|
try {
|
|
@@ -1346,22 +1741,6 @@ class WebhookClient {
|
|
|
1346
1741
|
}
|
|
1347
1742
|
|
|
1348
1743
|
// packages/rest/src/index.ts
|
|
1349
|
-
class RESTError extends Error {
|
|
1350
|
-
status;
|
|
1351
|
-
code;
|
|
1352
|
-
errors;
|
|
1353
|
-
method;
|
|
1354
|
-
path;
|
|
1355
|
-
constructor(message, status, code, errors, options = {}) {
|
|
1356
|
-
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
1357
|
-
this.name = "RESTError";
|
|
1358
|
-
this.status = status;
|
|
1359
|
-
this.code = code;
|
|
1360
|
-
this.errors = errors;
|
|
1361
|
-
this.method = options.method;
|
|
1362
|
-
this.path = options.path;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
1744
|
var LIBRARY_VERSION = package_default.version;
|
|
1366
1745
|
var LIBRARY_URL = "https://github.com/Ekretos/lunibee";
|
|
1367
1746
|
var USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${package_default.version})`;
|
|
@@ -1388,26 +1767,6 @@ function isRequestData(value) {
|
|
|
1388
1767
|
return false;
|
|
1389
1768
|
return keys.every((key) => REQUEST_DATA_KEYS.has(key));
|
|
1390
1769
|
}
|
|
1391
|
-
function serializeQuery(query) {
|
|
1392
|
-
if (query === undefined)
|
|
1393
|
-
return "";
|
|
1394
|
-
if (typeof query === "string")
|
|
1395
|
-
return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
|
|
1396
|
-
const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
|
|
1397
|
-
const serialized = params.toString();
|
|
1398
|
-
return serialized.length === 0 ? "" : `?${serialized}`;
|
|
1399
|
-
}
|
|
1400
|
-
function majorParameter(path) {
|
|
1401
|
-
const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/.exec(path);
|
|
1402
|
-
if (!match)
|
|
1403
|
-
return "@none";
|
|
1404
|
-
if (match[1] === "webhooks" && match[3] !== undefined)
|
|
1405
|
-
return `${match[2]}:${match[3]}`;
|
|
1406
|
-
return match[2];
|
|
1407
|
-
}
|
|
1408
|
-
function scopeBucket(hashOrRoute, major) {
|
|
1409
|
-
return `${hashOrRoute}|${major}`;
|
|
1410
|
-
}
|
|
1411
1770
|
function createRetryPolicy(maxRetries = 2) {
|
|
1412
1771
|
if (!Number.isFinite(maxRetries) || maxRetries < 0)
|
|
1413
1772
|
throw new TypeError("maxRetries must be a non-negative finite number.");
|
|
@@ -1415,7 +1774,10 @@ function createRetryPolicy(maxRetries = 2) {
|
|
|
1415
1774
|
maxRetries: Math.floor(maxRetries),
|
|
1416
1775
|
shouldRetry(method, status) {
|
|
1417
1776
|
const normalized = method.toUpperCase();
|
|
1418
|
-
|
|
1777
|
+
const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
|
|
1778
|
+
if (status === 0)
|
|
1779
|
+
return idempotent;
|
|
1780
|
+
return status === 429 || status >= 500 && status <= 599 && idempotent;
|
|
1419
1781
|
},
|
|
1420
1782
|
getDelay(attempt, retryAfter) {
|
|
1421
1783
|
if (retryAfter !== undefined)
|
|
@@ -1426,22 +1788,32 @@ function createRetryPolicy(maxRetries = 2) {
|
|
|
1426
1788
|
}
|
|
1427
1789
|
|
|
1428
1790
|
class REST {
|
|
1429
|
-
#baseURL;
|
|
1430
|
-
#timeout;
|
|
1431
1791
|
#retryPolicy;
|
|
1432
1792
|
#token;
|
|
1433
|
-
#
|
|
1434
|
-
#
|
|
1793
|
+
#limiter;
|
|
1794
|
+
#scheduler = new RequestScheduler;
|
|
1795
|
+
#transport;
|
|
1796
|
+
#decoder = new ResponseDecoder;
|
|
1435
1797
|
#hooks = {};
|
|
1798
|
+
#concurrentBuckets;
|
|
1436
1799
|
constructor(options = {}) {
|
|
1437
1800
|
this.#token = options.token;
|
|
1438
|
-
this.#timeout = Math.max(1, options.timeout ?? 15000);
|
|
1439
1801
|
this.#retryPolicy = options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);
|
|
1440
|
-
this.#
|
|
1441
|
-
|
|
1802
|
+
this.#transport = options.transport ?? new HttpTransport({
|
|
1803
|
+
baseURL: options.baseURL,
|
|
1804
|
+
timeout: options.timeout
|
|
1805
|
+
});
|
|
1806
|
+
const store = options.store ?? new MemoryRateLimitStore;
|
|
1807
|
+
this.#concurrentBuckets = (options.concurrentBuckets ?? false) && typeof store.reserve === "function";
|
|
1808
|
+
this.#limiter = new RateLimiter(store, {
|
|
1809
|
+
concurrent: this.#concurrentBuckets
|
|
1810
|
+
});
|
|
1442
1811
|
if (options.hooks)
|
|
1443
1812
|
this.#hooks = options.hooks;
|
|
1444
1813
|
}
|
|
1814
|
+
get store() {
|
|
1815
|
+
return this.#limiter.store;
|
|
1816
|
+
}
|
|
1445
1817
|
setHooks(hooks) {
|
|
1446
1818
|
this.#hooks = hooks;
|
|
1447
1819
|
return this;
|
|
@@ -1452,121 +1824,109 @@ class REST {
|
|
|
1452
1824
|
this.#token = token;
|
|
1453
1825
|
}
|
|
1454
1826
|
async request(method, path, body, options = {}) {
|
|
1455
|
-
|
|
1456
|
-
if (!normalizedMethod)
|
|
1827
|
+
if (!method.toUpperCase())
|
|
1457
1828
|
throw new TypeError("REST method is required.");
|
|
1458
1829
|
if (!path.startsWith("/"))
|
|
1459
1830
|
throw new TypeError("REST paths must start with '/'.");
|
|
1831
|
+
const route = createRouteKey(method, path);
|
|
1460
1832
|
const files = options.files;
|
|
1461
1833
|
if (files && files.length > 0 && !(body instanceof FormData))
|
|
1462
1834
|
body = this.#fileForm(body, files);
|
|
1463
1835
|
const query = serializeQuery(options.query);
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
let
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1836
|
+
const requestPath = query ? `${path}${query}` : path;
|
|
1837
|
+
return this.#scheduler.runResolved(() => this.#limiter.resolveBucketKey(route.route, route.major), options.signal, path, (bucketKey, release) => this.#attempts(route, requestPath, body, options, bucketKey, release));
|
|
1838
|
+
}
|
|
1839
|
+
async#attempts(route, requestPath, body, options, initialBucketKey, release) {
|
|
1840
|
+
const { method, path } = route;
|
|
1841
|
+
let bucketKey = initialBucketKey;
|
|
1842
|
+
for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
|
|
1843
|
+
await this.#limiter.acquire(bucketKey, options.signal, path);
|
|
1844
|
+
if (this.#concurrentBuckets) {
|
|
1845
|
+
const bucket = await this.#limiter.store.getBucket(bucketKey);
|
|
1846
|
+
if (bucket && bucket.resetAt > Date.now())
|
|
1847
|
+
release();
|
|
1848
|
+
}
|
|
1849
|
+
this.#emit(this.#hooks.onRequest, { method, path, attempt });
|
|
1850
|
+
const requestStart = Date.now();
|
|
1851
|
+
let response;
|
|
1852
|
+
try {
|
|
1853
|
+
response = await this.#transport.send({
|
|
1854
|
+
method,
|
|
1855
|
+
path: requestPath,
|
|
1856
|
+
headers: this.#headers(body, options),
|
|
1857
|
+
body: this.#encodeBody(body),
|
|
1858
|
+
signal: options.signal
|
|
1486
1859
|
});
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
if (options.
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
if (
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
headers["Content-Type"] = "application/json";
|
|
1497
|
-
const fetchBody = isFormData ? body : body === undefined ? undefined : JSON.stringify(body);
|
|
1498
|
-
try {
|
|
1499
|
-
this.#emit(this.#hooks.onRequest, {
|
|
1500
|
-
method: normalizedMethod,
|
|
1501
|
-
path,
|
|
1502
|
-
attempt
|
|
1503
|
-
});
|
|
1504
|
-
const requestStart = Date.now();
|
|
1505
|
-
const response = await fetch(`${this.#baseURL}${finalPath}`, {
|
|
1506
|
-
method: normalizedMethod,
|
|
1507
|
-
headers,
|
|
1508
|
-
body: fetchBody,
|
|
1509
|
-
signal: controller.signal
|
|
1510
|
-
});
|
|
1511
|
-
this.#emit(this.#hooks.onResponse, {
|
|
1512
|
-
method: normalizedMethod,
|
|
1513
|
-
path,
|
|
1514
|
-
status: response.status,
|
|
1515
|
-
durationMs: Date.now() - requestStart
|
|
1516
|
-
});
|
|
1517
|
-
bucketKey = await this.#update(response, bucketKey, route, major);
|
|
1518
|
-
const payload = await this.#readPayload(response);
|
|
1519
|
-
if (response.ok)
|
|
1520
|
-
return response.status === 204 ? undefined : payload;
|
|
1521
|
-
const data = this.#errorData(payload);
|
|
1522
|
-
const retryAfter = response.status === 429 ? this.#retryAfter(response, data) : undefined;
|
|
1523
|
-
if (data.global && retryAfter !== undefined) {
|
|
1524
|
-
const currentGlobal = await this.#store.getGlobalReset();
|
|
1525
|
-
await this.#store.setGlobalReset(Math.max(currentGlobal, Date.now() + retryAfter * 1000));
|
|
1526
|
-
}
|
|
1527
|
-
if (response.status === 429)
|
|
1528
|
-
this.#emit(this.#hooks.onRateLimit, {
|
|
1529
|
-
method: normalizedMethod,
|
|
1530
|
-
path,
|
|
1531
|
-
retryAfterMs: (retryAfter ?? 1) * 1000,
|
|
1532
|
-
global: data.global ?? false,
|
|
1533
|
-
bucket: await this.#store.getBucketHash(route)
|
|
1534
|
-
});
|
|
1535
|
-
if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, response.status)) {
|
|
1536
|
-
const delay = this.#retryPolicy.getDelay(attempt, retryAfter);
|
|
1537
|
-
this.#emit(this.#hooks.onRetry, {
|
|
1538
|
-
method: normalizedMethod,
|
|
1539
|
-
path,
|
|
1540
|
-
attempt,
|
|
1541
|
-
status: response.status,
|
|
1542
|
-
delayMs: delay
|
|
1543
|
-
});
|
|
1544
|
-
await this.#sleep(delay, options.signal, path);
|
|
1545
|
-
continue;
|
|
1546
|
-
}
|
|
1547
|
-
throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method: normalizedMethod, path });
|
|
1548
|
-
} catch (error) {
|
|
1549
|
-
if (error instanceof RESTError)
|
|
1550
|
-
throw error;
|
|
1551
|
-
if (options.signal?.aborted)
|
|
1552
|
-
throw this.#abortError(path, options.signal.reason);
|
|
1553
|
-
const isTimeout = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
1554
|
-
if (!isTimeout && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, 0)) {
|
|
1555
|
-
await this.#sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
|
|
1556
|
-
continue;
|
|
1557
|
-
}
|
|
1558
|
-
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 });
|
|
1559
|
-
} finally {
|
|
1560
|
-
clearTimeout(timer);
|
|
1561
|
-
options.signal?.removeEventListener("abort", onAbort);
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
if (error instanceof RESTError)
|
|
1862
|
+
throw error;
|
|
1863
|
+
if (options.signal?.aborted)
|
|
1864
|
+
throw abortError(path, options.signal.reason);
|
|
1865
|
+
const failure = error instanceof TransportError ? error : new TransportError("Discord REST request failed", false, error);
|
|
1866
|
+
if (!failure.timedOut && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, 0)) {
|
|
1867
|
+
await sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
|
|
1868
|
+
continue;
|
|
1562
1869
|
}
|
|
1870
|
+
throw new RESTError(failure.message, 0, undefined, undefined, {
|
|
1871
|
+
method,
|
|
1872
|
+
path,
|
|
1873
|
+
cause: failure.cause ?? failure
|
|
1874
|
+
});
|
|
1563
1875
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1876
|
+
this.#emit(this.#hooks.onResponse, {
|
|
1877
|
+
method,
|
|
1878
|
+
path,
|
|
1879
|
+
status: response.status,
|
|
1880
|
+
durationMs: Date.now() - requestStart
|
|
1881
|
+
});
|
|
1882
|
+
bucketKey = await this.#limiter.applyResponse(response, bucketKey, route.route, route.major);
|
|
1883
|
+
const payload = await this.#decoder.read(response);
|
|
1884
|
+
if (response.ok)
|
|
1885
|
+
return response.status === 204 ? undefined : payload;
|
|
1886
|
+
const data = this.#decoder.errorData(payload);
|
|
1887
|
+
const retryAfter = response.status === 429 ? this.#decoder.retryAfter(response, data) : undefined;
|
|
1888
|
+
if (data.global && retryAfter !== undefined)
|
|
1889
|
+
await this.#limiter.noteGlobalReset(Date.now() + retryAfter * 1000);
|
|
1890
|
+
if (response.status === 429)
|
|
1891
|
+
this.#emit(this.#hooks.onRateLimit, {
|
|
1892
|
+
method,
|
|
1893
|
+
path,
|
|
1894
|
+
retryAfterMs: (retryAfter ?? 1) * 1000,
|
|
1895
|
+
global: data.global ?? false,
|
|
1896
|
+
bucket: await this.#limiter.store.getBucketHash(route.route)
|
|
1897
|
+
});
|
|
1898
|
+
if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, response.status)) {
|
|
1899
|
+
const delay = this.#retryPolicy.getDelay(attempt, retryAfter);
|
|
1900
|
+
this.#emit(this.#hooks.onRetry, {
|
|
1901
|
+
method,
|
|
1902
|
+
path,
|
|
1903
|
+
attempt,
|
|
1904
|
+
status: response.status,
|
|
1905
|
+
delayMs: delay
|
|
1906
|
+
});
|
|
1907
|
+
await sleep(delay, options.signal, path);
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method, path });
|
|
1911
|
+
}
|
|
1912
|
+
throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method, path });
|
|
1913
|
+
}
|
|
1914
|
+
#headers(body, options) {
|
|
1915
|
+
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
1916
|
+
const headers = { ...options.headers };
|
|
1917
|
+
if (options.auth !== false && this.#token)
|
|
1918
|
+
headers["Authorization"] = `Bot ${this.#token}`;
|
|
1919
|
+
headers["User-Agent"] = USER_AGENT;
|
|
1920
|
+
if (options.reason !== undefined)
|
|
1921
|
+
headers["X-Audit-Log-Reason"] = encodeURIComponent(options.reason);
|
|
1922
|
+
if (!isFormData)
|
|
1923
|
+
headers["Content-Type"] = "application/json";
|
|
1924
|
+
return headers;
|
|
1925
|
+
}
|
|
1926
|
+
#encodeBody(body) {
|
|
1927
|
+
if (typeof FormData !== "undefined" && body instanceof FormData)
|
|
1928
|
+
return body;
|
|
1929
|
+
return body === undefined ? undefined : JSON.stringify(body);
|
|
1570
1930
|
}
|
|
1571
1931
|
#dispatch(method, path, bodyOrOptions, options) {
|
|
1572
1932
|
if (options === undefined && isRequestData(bodyOrOptions))
|
|
@@ -1611,115 +1971,6 @@ class REST {
|
|
|
1611
1971
|
return;
|
|
1612
1972
|
Promise.resolve().then(() => hook(ctx)).catch(() => {});
|
|
1613
1973
|
}
|
|
1614
|
-
async#wait(bucketKey, signal, path) {
|
|
1615
|
-
const bucket = await this.#store.getBucket(bucketKey);
|
|
1616
|
-
if (!bucket)
|
|
1617
|
-
return;
|
|
1618
|
-
const delay = bucket.resetAt - Date.now();
|
|
1619
|
-
if (bucket.remaining <= 0 && delay > 0)
|
|
1620
|
-
await this.#sleep(delay, signal, path);
|
|
1621
|
-
}
|
|
1622
|
-
async#waitGlobal(signal, path) {
|
|
1623
|
-
const globalResetAt = await this.#store.getGlobalReset();
|
|
1624
|
-
const delay = globalResetAt - Date.now();
|
|
1625
|
-
if (delay > 0)
|
|
1626
|
-
await this.#sleep(delay, signal, path);
|
|
1627
|
-
}
|
|
1628
|
-
async#sleep(delay, signal, path) {
|
|
1629
|
-
if (delay <= 0)
|
|
1630
|
-
return;
|
|
1631
|
-
await new Promise((resolve, reject) => {
|
|
1632
|
-
const timer = setTimeout(resolve, delay);
|
|
1633
|
-
const abort = () => {
|
|
1634
|
-
clearTimeout(timer);
|
|
1635
|
-
reject(this.#abortError(path, signal?.reason));
|
|
1636
|
-
};
|
|
1637
|
-
if (signal?.aborted)
|
|
1638
|
-
return abort();
|
|
1639
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
async#abortable(promise, signal, path) {
|
|
1643
|
-
if (!signal)
|
|
1644
|
-
return promise;
|
|
1645
|
-
if (signal.aborted)
|
|
1646
|
-
throw this.#abortError(path, signal.reason);
|
|
1647
|
-
return Promise.race([
|
|
1648
|
-
promise,
|
|
1649
|
-
new Promise((_, reject) => signal.addEventListener("abort", () => reject(this.#abortError(path, signal.reason)), { once: true }))
|
|
1650
|
-
]);
|
|
1651
|
-
}
|
|
1652
|
-
#abortError(path, reason) {
|
|
1653
|
-
return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
|
|
1654
|
-
}
|
|
1655
|
-
async#readPayload(response) {
|
|
1656
|
-
if (response.status === 204)
|
|
1657
|
-
return;
|
|
1658
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
1659
|
-
if (contentType.includes("application/json"))
|
|
1660
|
-
return response.json().catch(() => {
|
|
1661
|
-
return;
|
|
1662
|
-
});
|
|
1663
|
-
return response.text().catch(() => {
|
|
1664
|
-
return;
|
|
1665
|
-
});
|
|
1666
|
-
}
|
|
1667
|
-
#errorData(payload) {
|
|
1668
|
-
if (!payload || typeof payload !== "object")
|
|
1669
|
-
return {};
|
|
1670
|
-
const data = payload;
|
|
1671
|
-
return {
|
|
1672
|
-
message: typeof data.message === "string" ? data.message : undefined,
|
|
1673
|
-
code: typeof data.code === "number" ? data.code : undefined,
|
|
1674
|
-
retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
|
|
1675
|
-
global: data.global === true
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
#retryAfter(response, data) {
|
|
1679
|
-
const header = Number(response.headers.get("Retry-After"));
|
|
1680
|
-
const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
|
|
1681
|
-
return Math.max(0, retryAfter);
|
|
1682
|
-
}
|
|
1683
|
-
#normalizeRoute(path) {
|
|
1684
|
-
const queryIndex = path.indexOf("?");
|
|
1685
|
-
const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
|
|
1686
|
-
return routePath.replace(/\/\d+(?=\/|$)/g, "/:id");
|
|
1687
|
-
}
|
|
1688
|
-
async#update(response, bucketKey, route, major) {
|
|
1689
|
-
let currentKey = bucketKey;
|
|
1690
|
-
const serverBucket = response.headers.get("X-RateLimit-Bucket");
|
|
1691
|
-
if (serverBucket) {
|
|
1692
|
-
await this.#store.setBucketHash(route, serverBucket);
|
|
1693
|
-
currentKey = scopeBucket(serverBucket, major);
|
|
1694
|
-
}
|
|
1695
|
-
let bucket = await this.#store.getBucket(currentKey) ?? {
|
|
1696
|
-
remaining: 1,
|
|
1697
|
-
resetAt: 0
|
|
1698
|
-
};
|
|
1699
|
-
const remaining = Number(response.headers.get("X-RateLimit-Remaining"));
|
|
1700
|
-
const resetAfter = Number(response.headers.get("X-RateLimit-Reset-After"));
|
|
1701
|
-
const reset = Number(response.headers.get("X-RateLimit-Reset"));
|
|
1702
|
-
if (Number.isFinite(remaining))
|
|
1703
|
-
bucket.remaining = Math.max(0, remaining);
|
|
1704
|
-
if (Number.isFinite(resetAfter))
|
|
1705
|
-
bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;
|
|
1706
|
-
else if (Number.isFinite(reset))
|
|
1707
|
-
bucket.resetAt = reset * 1000;
|
|
1708
|
-
if (response.status === 429) {
|
|
1709
|
-
const retryAfter = Number(response.headers.get("Retry-After"));
|
|
1710
|
-
if (Number.isFinite(retryAfter))
|
|
1711
|
-
bucket.resetAt = Math.max(bucket.resetAt, Date.now() + retryAfter * 1000);
|
|
1712
|
-
}
|
|
1713
|
-
await this.#store.updateBucket(currentKey, bucket);
|
|
1714
|
-
if (response.headers.get("X-RateLimit-Global") === "true") {
|
|
1715
|
-
const retryAfter = Number(response.headers.get("Retry-After"));
|
|
1716
|
-
if (Number.isFinite(retryAfter)) {
|
|
1717
|
-
const currentGlobal = await this.#store.getGlobalReset();
|
|
1718
|
-
await this.#store.setGlobalReset(Math.max(currentGlobal, Date.now() + retryAfter * 1000));
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
return currentKey;
|
|
1722
|
-
}
|
|
1723
1974
|
}
|
|
1724
1975
|
|
|
1725
1976
|
// packages/structures/src/base.ts
|
|
@@ -1841,6 +2092,18 @@ class Channel extends BaseStructure {
|
|
|
1841
2092
|
update(options) {
|
|
1842
2093
|
return this.edit(options);
|
|
1843
2094
|
}
|
|
2095
|
+
isThread() {
|
|
2096
|
+
return this.type === 10 || this.type === 11 || this.type === 12;
|
|
2097
|
+
}
|
|
2098
|
+
isTextBased() {
|
|
2099
|
+
return [0, 1, 2, 3, 5, 10, 11, 12, 13].includes(this.type);
|
|
2100
|
+
}
|
|
2101
|
+
isVoiceBased() {
|
|
2102
|
+
return this.type === 2 || this.type === 13;
|
|
2103
|
+
}
|
|
2104
|
+
isDMBased() {
|
|
2105
|
+
return this.type === 1 || this.type === 3;
|
|
2106
|
+
}
|
|
1844
2107
|
toString() {
|
|
1845
2108
|
return `<#${this.id}>`;
|
|
1846
2109
|
}
|
|
@@ -2137,7 +2400,9 @@ class Emoji extends BaseStructure {
|
|
|
2137
2400
|
animated;
|
|
2138
2401
|
available;
|
|
2139
2402
|
constructor(data) {
|
|
2140
|
-
super(data.id ?? "
|
|
2403
|
+
super(data.id ?? "0");
|
|
2404
|
+
if (data.id === null)
|
|
2405
|
+
this.id = "unicode";
|
|
2141
2406
|
this.name = data.name;
|
|
2142
2407
|
this.roleIds = data.roles ?? [];
|
|
2143
2408
|
this.user = data.user ? new User(data.user) : null;
|
|
@@ -2270,14 +2535,107 @@ class GuildOnboarding extends BaseStructure {
|
|
|
2270
2535
|
this.mode = data.mode;
|
|
2271
2536
|
}
|
|
2272
2537
|
}
|
|
2273
|
-
// packages/structures/src/
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2538
|
+
// packages/structures/src/channels.ts
|
|
2539
|
+
class NewsChannel extends TextChannel {
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
class DMChannel extends Channel {
|
|
2543
|
+
recipients;
|
|
2544
|
+
ownerId;
|
|
2545
|
+
constructor(data, context) {
|
|
2546
|
+
super(data, context);
|
|
2547
|
+
this.recipients = (data.recipients ?? []).map((u) => new User(u));
|
|
2548
|
+
this.ownerId = data.owner_id ?? null;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
class VoiceChannel extends Channel {
|
|
2553
|
+
bitrate;
|
|
2554
|
+
userLimit;
|
|
2555
|
+
rtcRegion;
|
|
2556
|
+
videoQualityMode;
|
|
2557
|
+
constructor(data, context) {
|
|
2558
|
+
super(data, context);
|
|
2559
|
+
this.bitrate = data.bitrate ?? 64000;
|
|
2560
|
+
this.userLimit = data.user_limit ?? 0;
|
|
2561
|
+
this.rtcRegion = data.rtc_region ?? null;
|
|
2562
|
+
this.videoQualityMode = data.video_quality_mode ?? 1;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
class StageChannel extends VoiceChannel {
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
class CategoryChannel extends Channel {
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
class ThreadChannel extends Channel {
|
|
2573
|
+
ownerId;
|
|
2574
|
+
messageCount;
|
|
2575
|
+
memberCount;
|
|
2576
|
+
metadata;
|
|
2577
|
+
constructor(data, context) {
|
|
2578
|
+
super(data, context);
|
|
2579
|
+
this.ownerId = data.owner_id ?? null;
|
|
2580
|
+
this.messageCount = data.message_count ?? 0;
|
|
2581
|
+
this.memberCount = data.member_count ?? 0;
|
|
2582
|
+
this.metadata = data.thread_metadata ?? null;
|
|
2583
|
+
}
|
|
2584
|
+
get archived() {
|
|
2585
|
+
return this.metadata?.archived ?? false;
|
|
2586
|
+
}
|
|
2587
|
+
get locked() {
|
|
2588
|
+
return this.metadata?.locked ?? false;
|
|
2589
|
+
}
|
|
2590
|
+
get autoArchiveDuration() {
|
|
2591
|
+
return this.metadata?.auto_archive_duration ?? null;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
class ForumChannel extends Channel {
|
|
2596
|
+
availableTags;
|
|
2597
|
+
defaultReactionEmoji;
|
|
2598
|
+
defaultThreadRateLimitPerUser;
|
|
2599
|
+
defaultAutoArchiveDuration;
|
|
2600
|
+
constructor(data, context) {
|
|
2601
|
+
super(data, context);
|
|
2602
|
+
this.availableTags = data.available_tags ?? [];
|
|
2603
|
+
this.defaultReactionEmoji = data.default_reaction_emoji ?? null;
|
|
2604
|
+
this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user ?? 0;
|
|
2605
|
+
this.defaultAutoArchiveDuration = data.default_auto_archive_duration ?? null;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
class MediaChannel extends ForumChannel {
|
|
2610
|
+
}
|
|
2611
|
+
function createChannel(data, context) {
|
|
2612
|
+
switch (data.type) {
|
|
2613
|
+
case 0:
|
|
2614
|
+
return new TextChannel(data, context);
|
|
2615
|
+
case 1:
|
|
2616
|
+
case 3:
|
|
2617
|
+
return new DMChannel(data, context);
|
|
2618
|
+
case 2:
|
|
2619
|
+
return new VoiceChannel(data, context);
|
|
2620
|
+
case 4:
|
|
2621
|
+
return new CategoryChannel(data, context);
|
|
2622
|
+
case 5:
|
|
2623
|
+
return new NewsChannel(data, context);
|
|
2624
|
+
case 10:
|
|
2625
|
+
case 11:
|
|
2626
|
+
case 12:
|
|
2627
|
+
return new ThreadChannel(data, context);
|
|
2628
|
+
case 13:
|
|
2629
|
+
return new StageChannel(data, context);
|
|
2630
|
+
case 15:
|
|
2631
|
+
return new ForumChannel(data, context);
|
|
2632
|
+
case 16:
|
|
2633
|
+
return new MediaChannel(data, context);
|
|
2634
|
+
default:
|
|
2635
|
+
return new Channel(data, context);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
// packages/structures/src/options.ts
|
|
2281
2639
|
var OptionType = {
|
|
2282
2640
|
SubCommand: 1,
|
|
2283
2641
|
SubCommandGroup: 2,
|
|
@@ -2333,6 +2691,12 @@ class CommandOptions {
|
|
|
2333
2691
|
}
|
|
2334
2692
|
return this.#subcommandGroup;
|
|
2335
2693
|
}
|
|
2694
|
+
getFocused(full = false) {
|
|
2695
|
+
const focused = this.#options.find((o) => o.focused);
|
|
2696
|
+
if (!focused)
|
|
2697
|
+
throw new TypeError("No focused option was provided.");
|
|
2698
|
+
return full ? focused : focused.value ?? "";
|
|
2699
|
+
}
|
|
2336
2700
|
getString(name, required = false) {
|
|
2337
2701
|
const opt = this.#get(name);
|
|
2338
2702
|
if (!opt || opt.type !== OptionType.String) {
|
|
@@ -2438,6 +2802,14 @@ class CommandOptions {
|
|
|
2438
2802
|
return this.#options;
|
|
2439
2803
|
}
|
|
2440
2804
|
}
|
|
2805
|
+
// packages/structures/src/interactions.ts
|
|
2806
|
+
var InteractionType = {
|
|
2807
|
+
Ping: 1,
|
|
2808
|
+
ApplicationCommand: 2,
|
|
2809
|
+
MessageComponent: 3,
|
|
2810
|
+
ApplicationCommandAutocomplete: 4,
|
|
2811
|
+
ModalSubmit: 5
|
|
2812
|
+
};
|
|
2441
2813
|
|
|
2442
2814
|
class InteractionResponse {
|
|
2443
2815
|
type;
|
|
@@ -2501,6 +2873,30 @@ class Interaction {
|
|
|
2501
2873
|
isAutocomplete() {
|
|
2502
2874
|
return this.type === InteractionType.ApplicationCommandAutocomplete;
|
|
2503
2875
|
}
|
|
2876
|
+
#componentType() {
|
|
2877
|
+
return this.isMessageComponent() ? this.data.data?.component_type ?? 0 : 0;
|
|
2878
|
+
}
|
|
2879
|
+
isButton() {
|
|
2880
|
+
return this.#componentType() === 2;
|
|
2881
|
+
}
|
|
2882
|
+
isStringSelectMenu() {
|
|
2883
|
+
return this.#componentType() === 3;
|
|
2884
|
+
}
|
|
2885
|
+
isUserSelectMenu() {
|
|
2886
|
+
return this.#componentType() === 5;
|
|
2887
|
+
}
|
|
2888
|
+
isRoleSelectMenu() {
|
|
2889
|
+
return this.#componentType() === 6;
|
|
2890
|
+
}
|
|
2891
|
+
isMentionableSelectMenu() {
|
|
2892
|
+
return this.#componentType() === 7;
|
|
2893
|
+
}
|
|
2894
|
+
isChannelSelectMenu() {
|
|
2895
|
+
return this.#componentType() === 8;
|
|
2896
|
+
}
|
|
2897
|
+
isAnySelectMenu() {
|
|
2898
|
+
return [3, 5, 6, 7, 8].includes(this.#componentType());
|
|
2899
|
+
}
|
|
2504
2900
|
assertUnacknowledged() {
|
|
2505
2901
|
if (this.replied || this.deferred)
|
|
2506
2902
|
throw new Error("Interaction has already been acknowledged.");
|
|
@@ -2611,6 +3007,11 @@ class ModalSubmitInteraction extends Interaction {
|
|
|
2611
3007
|
}
|
|
2612
3008
|
|
|
2613
3009
|
class AutocompleteInteraction extends Interaction {
|
|
3010
|
+
options;
|
|
3011
|
+
constructor(client, data) {
|
|
3012
|
+
super(client, data);
|
|
3013
|
+
this.options = new CommandOptions(data.data?.options ?? []);
|
|
3014
|
+
}
|
|
2614
3015
|
get commandName() {
|
|
2615
3016
|
return this.data?.data?.name ?? "";
|
|
2616
3017
|
}
|
|
@@ -3034,7 +3435,7 @@ class ThreadManager {
|
|
|
3034
3435
|
auto_archive_duration: options.autoArchiveDuration,
|
|
3035
3436
|
rate_limit_per_user: options.rateLimitPerUser
|
|
3036
3437
|
});
|
|
3037
|
-
return
|
|
3438
|
+
return createChannel(data, this.#context);
|
|
3038
3439
|
}
|
|
3039
3440
|
}
|
|
3040
3441
|
|
|
@@ -3062,18 +3463,173 @@ class UserManager extends ResourceManager {
|
|
|
3062
3463
|
});
|
|
3063
3464
|
}
|
|
3064
3465
|
}
|
|
3065
|
-
// packages/managers/src/guild.ts
|
|
3466
|
+
// packages/managers/src/guild-resources.ts
|
|
3066
3467
|
function withQuery(path, params) {
|
|
3468
|
+
const query = new URLSearchParams;
|
|
3469
|
+
for (const [key, value] of Object.entries(params))
|
|
3470
|
+
if (value !== undefined)
|
|
3471
|
+
query.set(key, String(value));
|
|
3472
|
+
const suffix = query.toString();
|
|
3473
|
+
return suffix ? `${path}?${suffix}` : path;
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
class GuildBanManager extends Manager {
|
|
3477
|
+
#rest;
|
|
3478
|
+
guildId;
|
|
3479
|
+
constructor(rest, guildId) {
|
|
3480
|
+
super();
|
|
3481
|
+
this.#rest = rest;
|
|
3482
|
+
this.guildId = guildId;
|
|
3483
|
+
}
|
|
3484
|
+
async fetch(userId) {
|
|
3485
|
+
const ban = await this.#rest.get(Routes.guildBan(this.guildId, userId));
|
|
3486
|
+
this.set(ban.user.id, ban);
|
|
3487
|
+
return ban;
|
|
3488
|
+
}
|
|
3489
|
+
async fetchAll(options = {}) {
|
|
3490
|
+
const limit = options.limit === undefined ? undefined : Math.min(1000, Math.max(1, options.limit));
|
|
3491
|
+
const bans = await this.#rest.get(withQuery(Routes.guildBans(this.guildId), {
|
|
3492
|
+
limit,
|
|
3493
|
+
before: options.before,
|
|
3494
|
+
after: options.after
|
|
3495
|
+
}));
|
|
3496
|
+
for (const ban of bans)
|
|
3497
|
+
this.set(ban.user.id, ban);
|
|
3498
|
+
return bans;
|
|
3499
|
+
}
|
|
3500
|
+
async create(userId, options = {}) {
|
|
3501
|
+
await this.#rest.put(Routes.guildBan(this.guildId, userId), { delete_message_seconds: options.deleteMessageSeconds }, { reason: options.reason });
|
|
3502
|
+
}
|
|
3503
|
+
async remove(userId, reason) {
|
|
3504
|
+
await this.#rest.delete(Routes.guildBan(this.guildId, userId), {
|
|
3505
|
+
reason
|
|
3506
|
+
});
|
|
3507
|
+
this.delete(userId);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
class GuildScheduledEventManager extends Manager {
|
|
3512
|
+
#rest;
|
|
3513
|
+
guildId;
|
|
3514
|
+
constructor(rest, guildId) {
|
|
3515
|
+
super();
|
|
3516
|
+
this.#rest = rest;
|
|
3517
|
+
this.guildId = guildId;
|
|
3518
|
+
}
|
|
3519
|
+
#store(event) {
|
|
3520
|
+
this.set(event.id, event);
|
|
3521
|
+
return event;
|
|
3522
|
+
}
|
|
3523
|
+
async fetch(eventId, options = {}) {
|
|
3524
|
+
return this.#store(await this.#rest.get(withQuery(Routes.guildScheduledEvent(this.guildId, eventId), {
|
|
3525
|
+
with_user_count: options.withUserCount
|
|
3526
|
+
})));
|
|
3527
|
+
}
|
|
3528
|
+
async fetchAll(options = {}) {
|
|
3529
|
+
const events = await this.#rest.get(withQuery(Routes.guildScheduledEvents(this.guildId), {
|
|
3530
|
+
with_user_count: options.withUserCount
|
|
3531
|
+
}));
|
|
3532
|
+
return events.map((event) => this.#store(event));
|
|
3533
|
+
}
|
|
3534
|
+
async create(options, reason) {
|
|
3535
|
+
return this.#store(await this.#rest.post(Routes.guildScheduledEvents(this.guildId), options, { reason }));
|
|
3536
|
+
}
|
|
3537
|
+
async edit(eventId, options, reason) {
|
|
3538
|
+
return this.#store(await this.#rest.patch(Routes.guildScheduledEvent(this.guildId, eventId), options, { reason }));
|
|
3539
|
+
}
|
|
3540
|
+
async remove(eventId) {
|
|
3541
|
+
await this.#rest.delete(Routes.guildScheduledEvent(this.guildId, eventId));
|
|
3542
|
+
this.delete(eventId);
|
|
3543
|
+
}
|
|
3544
|
+
async fetchSubscribers(eventId, options = {}) {
|
|
3545
|
+
const limit = options.limit === undefined ? undefined : Math.min(100, Math.max(1, options.limit));
|
|
3546
|
+
return this.#rest.get(withQuery(Routes.guildScheduledEventUsers(this.guildId, eventId), {
|
|
3547
|
+
limit,
|
|
3548
|
+
with_member: options.withMember,
|
|
3549
|
+
before: options.before,
|
|
3550
|
+
after: options.after
|
|
3551
|
+
}));
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
class StageInstanceManager extends Manager {
|
|
3556
|
+
#rest;
|
|
3557
|
+
constructor(rest) {
|
|
3558
|
+
super();
|
|
3559
|
+
this.#rest = rest;
|
|
3560
|
+
}
|
|
3561
|
+
#store(stage) {
|
|
3562
|
+
this.set(stage.channel_id, stage);
|
|
3563
|
+
return stage;
|
|
3564
|
+
}
|
|
3565
|
+
async create(channelId, options, reason) {
|
|
3566
|
+
return this.#store(await this.#rest.post(Routes.stageInstances(), {
|
|
3567
|
+
channel_id: channelId,
|
|
3568
|
+
topic: options.topic,
|
|
3569
|
+
privacy_level: options.privacyLevel,
|
|
3570
|
+
send_start_notification: options.sendStartNotification,
|
|
3571
|
+
guild_scheduled_event_id: options.guildScheduledEventId
|
|
3572
|
+
}, { reason }));
|
|
3573
|
+
}
|
|
3574
|
+
async fetch(channelId) {
|
|
3575
|
+
return this.#store(await this.#rest.get(Routes.stageInstanceByChannel(channelId)));
|
|
3576
|
+
}
|
|
3577
|
+
async edit(channelId, options, reason) {
|
|
3578
|
+
return this.#store(await this.#rest.patch(Routes.stageInstanceByChannel(channelId), { topic: options.topic, privacy_level: options.privacyLevel }, { reason }));
|
|
3579
|
+
}
|
|
3580
|
+
async remove(channelId, reason) {
|
|
3581
|
+
await this.#rest.delete(Routes.stageInstanceByChannel(channelId), {
|
|
3582
|
+
reason
|
|
3583
|
+
});
|
|
3584
|
+
this.delete(channelId);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
class PermissionOverwriteManager {
|
|
3589
|
+
#rest;
|
|
3590
|
+
channelId;
|
|
3591
|
+
constructor(rest, channelId) {
|
|
3592
|
+
this.#rest = rest;
|
|
3593
|
+
this.channelId = channelId;
|
|
3594
|
+
}
|
|
3595
|
+
async edit(overwriteId, options, reason) {
|
|
3596
|
+
await this.#rest.put(Routes.channelPermission(this.channelId, overwriteId), {
|
|
3597
|
+
type: options.type,
|
|
3598
|
+
allow: BigInt(options.allow ?? 0).toString(),
|
|
3599
|
+
deny: BigInt(options.deny ?? 0).toString()
|
|
3600
|
+
}, { reason });
|
|
3601
|
+
}
|
|
3602
|
+
async remove(overwriteId, reason) {
|
|
3603
|
+
await this.#rest.delete(Routes.channelPermission(this.channelId, overwriteId), { reason });
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
// packages/managers/src/guild.ts
|
|
3608
|
+
function withQuery2(path, params) {
|
|
3067
3609
|
const suffix = params.toString();
|
|
3068
3610
|
return suffix ? `${path}?${suffix}` : path;
|
|
3069
3611
|
}
|
|
3070
3612
|
|
|
3071
3613
|
class GuildManager extends ResourceManager {
|
|
3072
3614
|
#rest;
|
|
3615
|
+
#bans = new Map;
|
|
3616
|
+
#scheduledEvents = new Map;
|
|
3073
3617
|
constructor(rest) {
|
|
3074
3618
|
super(async (id) => new Guild(await rest.get(Routes.guild(id))), (guild) => guild.id);
|
|
3075
3619
|
this.#rest = rest;
|
|
3076
3620
|
}
|
|
3621
|
+
bans(guildId) {
|
|
3622
|
+
let manager = this.#bans.get(guildId);
|
|
3623
|
+
if (!manager)
|
|
3624
|
+
this.#bans.set(guildId, manager = new GuildBanManager(this.#rest, guildId));
|
|
3625
|
+
return manager;
|
|
3626
|
+
}
|
|
3627
|
+
scheduledEvents(guildId) {
|
|
3628
|
+
let manager = this.#scheduledEvents.get(guildId);
|
|
3629
|
+
if (!manager)
|
|
3630
|
+
this.#scheduledEvents.set(guildId, manager = new GuildScheduledEventManager(this.#rest, guildId));
|
|
3631
|
+
return manager;
|
|
3632
|
+
}
|
|
3077
3633
|
async create(options) {
|
|
3078
3634
|
const data = await this.#rest.post("/guilds", options);
|
|
3079
3635
|
return this.upsert(new Guild(data));
|
|
@@ -3109,7 +3665,7 @@ class GuildManager extends ResourceManager {
|
|
|
3109
3665
|
params.set("after", options.after);
|
|
3110
3666
|
if (options.limit !== undefined)
|
|
3111
3667
|
params.set("limit", String(Math.min(100, Math.max(1, options.limit))));
|
|
3112
|
-
return this.#rest.get(
|
|
3668
|
+
return this.#rest.get(withQuery2(Routes.guildAuditLog(guildId), params));
|
|
3113
3669
|
}
|
|
3114
3670
|
async fetchMembers(guildId, options = {}) {
|
|
3115
3671
|
const params = new URLSearchParams;
|
|
@@ -3117,7 +3673,7 @@ class GuildManager extends ResourceManager {
|
|
|
3117
3673
|
params.set("limit", String(Math.min(1000, Math.max(1, options.limit))));
|
|
3118
3674
|
if (options.after)
|
|
3119
3675
|
params.set("after", options.after);
|
|
3120
|
-
return this.#rest.get(
|
|
3676
|
+
return this.#rest.get(withQuery2(Routes.guildMembers(guildId), params));
|
|
3121
3677
|
}
|
|
3122
3678
|
async fetchAutoModerationRules(guildId) {
|
|
3123
3679
|
const rules = await this.#rest.get(Routes.guildAutoModerationRules(guildId));
|
|
@@ -3248,9 +3804,7 @@ class GuildMemberManager extends ResourceManager {
|
|
|
3248
3804
|
this.delete(userId);
|
|
3249
3805
|
}
|
|
3250
3806
|
async ban(userId, options = {}) {
|
|
3251
|
-
await this.#rest.put(Routes.guildBan(this.guildId, userId), {
|
|
3252
|
-
delete_message_seconds: options.deleteMessageSeconds
|
|
3253
|
-
});
|
|
3807
|
+
await this.#rest.put(Routes.guildBan(this.guildId, userId), { delete_message_seconds: options.deleteMessageSeconds }, { reason: options.reason });
|
|
3254
3808
|
this.delete(userId);
|
|
3255
3809
|
}
|
|
3256
3810
|
async unban(userId) {
|
|
@@ -3382,6 +3936,9 @@ class ChannelManager extends Manager {
|
|
|
3382
3936
|
}
|
|
3383
3937
|
return manager;
|
|
3384
3938
|
}
|
|
3939
|
+
permissionOverwrites(channelId) {
|
|
3940
|
+
return new PermissionOverwriteManager(this.#rest, channelId);
|
|
3941
|
+
}
|
|
3385
3942
|
threads(channelId) {
|
|
3386
3943
|
return new ThreadManager(this.#rest, this.#context, channelId);
|
|
3387
3944
|
}
|
|
@@ -3393,7 +3950,7 @@ class ChannelManager extends Manager {
|
|
|
3393
3950
|
}
|
|
3394
3951
|
upsert(data) {
|
|
3395
3952
|
const existing = this.get(data.id);
|
|
3396
|
-
const channel =
|
|
3953
|
+
const channel = createChannel(data, this.#context);
|
|
3397
3954
|
if (existing) {
|
|
3398
3955
|
Object.assign(existing, channel);
|
|
3399
3956
|
return existing;
|
|
@@ -3501,35 +4058,593 @@ class ChannelManager extends Manager {
|
|
|
3501
4058
|
async unpinMessage(channelId, messageId) {
|
|
3502
4059
|
await this.#rest.delete(Routes.channelPin(channelId, messageId));
|
|
3503
4060
|
}
|
|
3504
|
-
createThreadFromMessage(channelId, messageId, options) {
|
|
3505
|
-
return this.threads(channelId).createFromMessage(messageId, options);
|
|
4061
|
+
createThreadFromMessage(channelId, messageId, options) {
|
|
4062
|
+
return this.threads(channelId).createFromMessage(messageId, options);
|
|
4063
|
+
}
|
|
4064
|
+
async fetchPage(channelId, options = {}) {
|
|
4065
|
+
const limit = Math.min(100, Math.max(1, options.limit ?? 50));
|
|
4066
|
+
const messages = await this.fetchMessages(channelId, {
|
|
4067
|
+
...options,
|
|
4068
|
+
limit
|
|
4069
|
+
});
|
|
4070
|
+
return {
|
|
4071
|
+
messages,
|
|
4072
|
+
before: messages[0]?.id,
|
|
4073
|
+
after: messages[messages.length - 1]?.id,
|
|
4074
|
+
hasMore: messages.length === limit
|
|
4075
|
+
};
|
|
4076
|
+
}
|
|
4077
|
+
async bulkDelete(channelId, messageIds) {
|
|
4078
|
+
return this.bulkDeleteMessages(channelId, messageIds);
|
|
4079
|
+
}
|
|
4080
|
+
clear() {
|
|
4081
|
+
this.#messageManagers.clear();
|
|
4082
|
+
super.clear();
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
function isMessageQuery(value) {
|
|
4086
|
+
return typeof value === "object" && value !== null && !(Symbol.iterator in value);
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
// packages/ws/src/session.ts
|
|
4090
|
+
class GatewaySession {
|
|
4091
|
+
#state = { status: "none" };
|
|
4092
|
+
#sequence = null;
|
|
4093
|
+
#generation = 0;
|
|
4094
|
+
#lastInvalidation;
|
|
4095
|
+
get lastInvalidation() {
|
|
4096
|
+
return this.#lastInvalidation;
|
|
4097
|
+
}
|
|
4098
|
+
get generation() {
|
|
4099
|
+
return this.#generation;
|
|
4100
|
+
}
|
|
4101
|
+
get sessionId() {
|
|
4102
|
+
return this.#state.status === "established" ? this.#state.sessionId : undefined;
|
|
4103
|
+
}
|
|
4104
|
+
get resumeURL() {
|
|
4105
|
+
return this.#state.status === "established" ? this.#state.resumeURL : undefined;
|
|
4106
|
+
}
|
|
4107
|
+
get sequence() {
|
|
4108
|
+
return this.#sequence;
|
|
4109
|
+
}
|
|
4110
|
+
get canResume() {
|
|
4111
|
+
return this.#state.status === "established" && this.#sequence !== null;
|
|
4112
|
+
}
|
|
4113
|
+
beginConnection() {
|
|
4114
|
+
return ++this.#generation;
|
|
4115
|
+
}
|
|
4116
|
+
owns(token) {
|
|
4117
|
+
return token === this.#generation;
|
|
4118
|
+
}
|
|
4119
|
+
recordSequence(sequence, token) {
|
|
4120
|
+
if (!this.owns(token))
|
|
4121
|
+
return false;
|
|
4122
|
+
if (!Number.isInteger(sequence))
|
|
4123
|
+
return false;
|
|
4124
|
+
this.#sequence = sequence;
|
|
4125
|
+
return true;
|
|
4126
|
+
}
|
|
4127
|
+
activate(payload, token) {
|
|
4128
|
+
if (!this.owns(token))
|
|
4129
|
+
return false;
|
|
4130
|
+
const ready = payload;
|
|
4131
|
+
if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string")
|
|
4132
|
+
return false;
|
|
4133
|
+
this.#state = {
|
|
4134
|
+
status: "established",
|
|
4135
|
+
sessionId: ready.session_id,
|
|
4136
|
+
resumeURL: ready.resume_gateway_url
|
|
4137
|
+
};
|
|
4138
|
+
return true;
|
|
4139
|
+
}
|
|
4140
|
+
invalidate(token, reason = "explicit") {
|
|
4141
|
+
if (!this.owns(token))
|
|
4142
|
+
return false;
|
|
4143
|
+
this.#lastInvalidation = reason;
|
|
4144
|
+
this.#state = { status: "none" };
|
|
4145
|
+
this.#sequence = null;
|
|
4146
|
+
return true;
|
|
4147
|
+
}
|
|
4148
|
+
resumeInfo() {
|
|
4149
|
+
if (this.#state.status !== "established")
|
|
4150
|
+
return;
|
|
4151
|
+
if (this.#sequence === null)
|
|
4152
|
+
return;
|
|
4153
|
+
const { sessionId, resumeURL } = this.#state;
|
|
4154
|
+
return { sessionId, sequence: this.#sequence, resumeURL };
|
|
4155
|
+
}
|
|
4156
|
+
handshake() {
|
|
4157
|
+
const resume = this.resumeInfo();
|
|
4158
|
+
return resume ? { type: "resume", ...resume } : { type: "identify" };
|
|
4159
|
+
}
|
|
4160
|
+
connectURL(fallback) {
|
|
4161
|
+
return this.resumeInfo()?.resumeURL ?? fallback;
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
// packages/ws/src/heartbeat.ts
|
|
4166
|
+
var MAX_MONITOR_INTERVAL = 1000;
|
|
4167
|
+
|
|
4168
|
+
class GatewayHeartbeat {
|
|
4169
|
+
#options;
|
|
4170
|
+
#now;
|
|
4171
|
+
#interval = 0;
|
|
4172
|
+
#acknowledged = true;
|
|
4173
|
+
#sentAt = 0;
|
|
4174
|
+
#latency = -1;
|
|
4175
|
+
#lastMessageAt = 0;
|
|
4176
|
+
#zombieReported = false;
|
|
4177
|
+
#timer;
|
|
4178
|
+
#initialTimer;
|
|
4179
|
+
#ackTimer;
|
|
4180
|
+
#monitorTimer;
|
|
4181
|
+
constructor(options) {
|
|
4182
|
+
if (options.zombieTimeout <= options.ackTimeout)
|
|
4183
|
+
throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
|
|
4184
|
+
this.#options = options;
|
|
4185
|
+
this.#now = options.now ?? (() => Date.now());
|
|
4186
|
+
}
|
|
4187
|
+
get latency() {
|
|
4188
|
+
return this.#latency;
|
|
4189
|
+
}
|
|
4190
|
+
get interval() {
|
|
4191
|
+
return this.#interval;
|
|
4192
|
+
}
|
|
4193
|
+
get acknowledged() {
|
|
4194
|
+
return this.#acknowledged;
|
|
4195
|
+
}
|
|
4196
|
+
get silentFor() {
|
|
4197
|
+
return this.#lastMessageAt === 0 ? 0 : this.#now() - this.#lastMessageAt;
|
|
4198
|
+
}
|
|
4199
|
+
get stalenessDeadline() {
|
|
4200
|
+
return Math.max(this.#options.zombieTimeout, this.#interval + this.#options.ackTimeout);
|
|
4201
|
+
}
|
|
4202
|
+
isHealthy() {
|
|
4203
|
+
if (!this.#acknowledged)
|
|
4204
|
+
return false;
|
|
4205
|
+
if (this.#lastMessageAt === 0)
|
|
4206
|
+
return true;
|
|
4207
|
+
return this.silentFor < this.stalenessDeadline;
|
|
4208
|
+
}
|
|
4209
|
+
watch() {
|
|
4210
|
+
this.#lastMessageAt = this.#now();
|
|
4211
|
+
this.#zombieReported = false;
|
|
4212
|
+
this.#startMonitor();
|
|
4213
|
+
}
|
|
4214
|
+
receivedMessage() {
|
|
4215
|
+
this.#lastMessageAt = this.#now();
|
|
4216
|
+
this.#zombieReported = false;
|
|
4217
|
+
}
|
|
4218
|
+
start(interval) {
|
|
4219
|
+
this.#clearBeatTimers();
|
|
4220
|
+
this.#interval = interval;
|
|
4221
|
+
this.#acknowledged = true;
|
|
4222
|
+
this.#initialTimer = setTimeout(() => this.sendHeartbeat(), Math.random() * interval);
|
|
4223
|
+
this.#timer = setInterval(() => this.sendHeartbeat(), interval);
|
|
4224
|
+
}
|
|
4225
|
+
stop() {
|
|
4226
|
+
this.#clearBeatTimers();
|
|
4227
|
+
if (this.#monitorTimer)
|
|
4228
|
+
clearInterval(this.#monitorTimer);
|
|
4229
|
+
this.#monitorTimer = undefined;
|
|
4230
|
+
}
|
|
4231
|
+
sendHeartbeat() {
|
|
4232
|
+
this.#acknowledged = false;
|
|
4233
|
+
this.#sentAt = this.#now();
|
|
4234
|
+
if (!this.#options.send(this.#options.sequence())) {
|
|
4235
|
+
this.#options.onError(new Error("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
4236
|
+
return;
|
|
4237
|
+
}
|
|
4238
|
+
this.#clearAckTimer();
|
|
4239
|
+
this.#ackTimer = setTimeout(() => {
|
|
4240
|
+
if (this.#acknowledged)
|
|
4241
|
+
return;
|
|
4242
|
+
this.#options.onTimeout({
|
|
4243
|
+
type: "ack",
|
|
4244
|
+
elapsedMs: this.#now() - this.#sentAt
|
|
4245
|
+
});
|
|
4246
|
+
}, this.#options.ackTimeout);
|
|
4247
|
+
}
|
|
4248
|
+
acknowledge() {
|
|
4249
|
+
this.#acknowledged = true;
|
|
4250
|
+
this.#latency = this.#now() - this.#sentAt;
|
|
4251
|
+
this.#clearAckTimer();
|
|
4252
|
+
}
|
|
4253
|
+
#startMonitor() {
|
|
4254
|
+
if (this.#monitorTimer)
|
|
4255
|
+
clearInterval(this.#monitorTimer);
|
|
4256
|
+
const interval = Math.max(1, Math.min(MAX_MONITOR_INTERVAL, this.#options.ackTimeout, this.#options.zombieTimeout / 2));
|
|
4257
|
+
this.#monitorTimer = setInterval(() => this.#checkStaleness(), interval);
|
|
4258
|
+
}
|
|
4259
|
+
#checkStaleness() {
|
|
4260
|
+
if (!this.#options.isConnected() || this.#lastMessageAt === 0)
|
|
4261
|
+
return;
|
|
4262
|
+
const silentFor = this.silentFor;
|
|
4263
|
+
const deadline = this.stalenessDeadline;
|
|
4264
|
+
if (silentFor < deadline || this.#zombieReported)
|
|
4265
|
+
return;
|
|
4266
|
+
this.#zombieReported = true;
|
|
4267
|
+
this.#options.onTimeout({ type: "zombie", silentFor, deadline });
|
|
4268
|
+
}
|
|
4269
|
+
#clearAckTimer() {
|
|
4270
|
+
if (this.#ackTimer)
|
|
4271
|
+
clearTimeout(this.#ackTimer);
|
|
4272
|
+
this.#ackTimer = undefined;
|
|
4273
|
+
}
|
|
4274
|
+
#clearBeatTimers() {
|
|
4275
|
+
if (this.#timer)
|
|
4276
|
+
clearInterval(this.#timer);
|
|
4277
|
+
if (this.#initialTimer)
|
|
4278
|
+
clearTimeout(this.#initialTimer);
|
|
4279
|
+
this.#clearAckTimer();
|
|
4280
|
+
this.#timer = undefined;
|
|
4281
|
+
this.#initialTimer = undefined;
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
|
|
4285
|
+
// packages/ws/src/close-codes.ts
|
|
4286
|
+
var GatewayCloseCodes = {
|
|
4287
|
+
UnknownError: 4000,
|
|
4288
|
+
UnknownOpcode: 4001,
|
|
4289
|
+
DecodeError: 4002,
|
|
4290
|
+
NotAuthenticated: 4003,
|
|
4291
|
+
AuthenticationFailed: 4004,
|
|
4292
|
+
AlreadyAuthenticated: 4005,
|
|
4293
|
+
InvalidSeq: 4007,
|
|
4294
|
+
RateLimited: 4008,
|
|
4295
|
+
SessionTimedOut: 4009,
|
|
4296
|
+
InvalidShard: 4010,
|
|
4297
|
+
ShardingRequired: 4011,
|
|
4298
|
+
InvalidAPIVersion: 4012,
|
|
4299
|
+
InvalidIntents: 4013,
|
|
4300
|
+
DisallowedIntents: 4014
|
|
4301
|
+
};
|
|
4302
|
+
|
|
4303
|
+
// packages/ws/src/reconnect.ts
|
|
4304
|
+
var FATAL_CLOSE_CODES = [
|
|
4305
|
+
GatewayCloseCodes.AuthenticationFailed,
|
|
4306
|
+
GatewayCloseCodes.InvalidShard,
|
|
4307
|
+
GatewayCloseCodes.ShardingRequired,
|
|
4308
|
+
GatewayCloseCodes.InvalidAPIVersion,
|
|
4309
|
+
GatewayCloseCodes.InvalidIntents,
|
|
4310
|
+
GatewayCloseCodes.DisallowedIntents
|
|
4311
|
+
];
|
|
4312
|
+
var IDENTIFY_CLOSE_CODES = [
|
|
4313
|
+
GatewayCloseCodes.InvalidSeq,
|
|
4314
|
+
GatewayCloseCodes.SessionTimedOut
|
|
4315
|
+
];
|
|
4316
|
+
function classifyCloseCode(code, canResume) {
|
|
4317
|
+
if (FATAL_CLOSE_CODES.includes(code))
|
|
4318
|
+
return "stop";
|
|
4319
|
+
if (IDENTIFY_CLOSE_CODES.includes(code))
|
|
4320
|
+
return "identify";
|
|
4321
|
+
return canResume ? "resume" : "identify";
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
class GatewayReconnect {
|
|
4325
|
+
#options;
|
|
4326
|
+
#random;
|
|
4327
|
+
#attempt = 0;
|
|
4328
|
+
#timer;
|
|
4329
|
+
#promise;
|
|
4330
|
+
#resolve;
|
|
4331
|
+
#reject;
|
|
4332
|
+
constructor(options) {
|
|
4333
|
+
this.#options = options;
|
|
4334
|
+
this.#random = options.random ?? Math.random;
|
|
4335
|
+
}
|
|
4336
|
+
get enabled() {
|
|
4337
|
+
return this.#options.enabled;
|
|
4338
|
+
}
|
|
4339
|
+
get attempts() {
|
|
4340
|
+
return this.#attempt;
|
|
4341
|
+
}
|
|
4342
|
+
get pending() {
|
|
4343
|
+
return this.#timer !== undefined;
|
|
4344
|
+
}
|
|
4345
|
+
get exhausted() {
|
|
4346
|
+
return this.#attempt >= this.#options.maxAttempts;
|
|
4347
|
+
}
|
|
4348
|
+
get connecting() {
|
|
4349
|
+
return this.#promise !== undefined;
|
|
4350
|
+
}
|
|
4351
|
+
get attemptPromise() {
|
|
4352
|
+
return this.#promise;
|
|
4353
|
+
}
|
|
4354
|
+
classifyClose(code, canResume) {
|
|
4355
|
+
return classifyCloseCode(code, canResume);
|
|
4356
|
+
}
|
|
4357
|
+
canReconnect() {
|
|
4358
|
+
return this.#options.enabled && !this.exhausted;
|
|
4359
|
+
}
|
|
4360
|
+
schedule(run) {
|
|
4361
|
+
if (!this.#options.enabled)
|
|
4362
|
+
return { scheduled: false, reason: "disabled" };
|
|
4363
|
+
if (this.#timer)
|
|
4364
|
+
return { scheduled: false, reason: "pending" };
|
|
4365
|
+
if (this.exhausted)
|
|
4366
|
+
return { scheduled: false, reason: "exhausted" };
|
|
4367
|
+
const attempt = this.#attempt;
|
|
4368
|
+
const delay = Math.min(this.#options.maxDelay, this.#options.baseDelay * 2 ** this.#attempt++);
|
|
4369
|
+
const jitter = this.#random() * Math.max(1, delay * 0.25);
|
|
4370
|
+
const delayMs = delay + jitter;
|
|
4371
|
+
this.#timer = setTimeout(() => {
|
|
4372
|
+
this.#timer = undefined;
|
|
4373
|
+
run();
|
|
4374
|
+
}, delayMs);
|
|
4375
|
+
return { scheduled: true, delayMs, attempt };
|
|
4376
|
+
}
|
|
4377
|
+
cancel() {
|
|
4378
|
+
if (this.#timer)
|
|
4379
|
+
clearTimeout(this.#timer);
|
|
4380
|
+
this.#timer = undefined;
|
|
4381
|
+
}
|
|
4382
|
+
reset() {
|
|
4383
|
+
this.#attempt = 0;
|
|
4384
|
+
}
|
|
4385
|
+
attempt(start) {
|
|
4386
|
+
if (this.#promise)
|
|
4387
|
+
return this.#promise;
|
|
4388
|
+
this.#promise = new Promise((resolve, reject) => {
|
|
4389
|
+
this.#resolve = resolve;
|
|
4390
|
+
this.#reject = reject;
|
|
4391
|
+
start();
|
|
4392
|
+
});
|
|
4393
|
+
return this.#promise;
|
|
4394
|
+
}
|
|
4395
|
+
settle(error) {
|
|
4396
|
+
const resolve = this.#resolve;
|
|
4397
|
+
const reject = this.#reject;
|
|
4398
|
+
this.#resolve = undefined;
|
|
4399
|
+
this.#reject = undefined;
|
|
4400
|
+
this.#promise = undefined;
|
|
4401
|
+
if (error)
|
|
4402
|
+
reject?.(error);
|
|
4403
|
+
else
|
|
4404
|
+
resolve?.();
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
// packages/ws/src/decoder.ts
|
|
4409
|
+
import {
|
|
4410
|
+
createInflate,
|
|
4411
|
+
constants as zlibConstants
|
|
4412
|
+
} from "zlib";
|
|
4413
|
+
async function toBytes(data) {
|
|
4414
|
+
if (data instanceof Uint8Array)
|
|
4415
|
+
return data;
|
|
4416
|
+
if (data instanceof ArrayBuffer)
|
|
4417
|
+
return new Uint8Array(data);
|
|
4418
|
+
if (typeof Blob !== "undefined" && data instanceof Blob)
|
|
4419
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
4420
|
+
throw new TypeError("Gateway returned an unsupported compressed frame type.");
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
class PlainTextDecoder {
|
|
4424
|
+
push(data) {
|
|
4425
|
+
return [String(data)];
|
|
4426
|
+
}
|
|
4427
|
+
reset() {}
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
class ZlibStreamDecoder {
|
|
4431
|
+
#create;
|
|
4432
|
+
#inflate;
|
|
4433
|
+
#chunks = [];
|
|
4434
|
+
#failure;
|
|
4435
|
+
#pendingReject;
|
|
4436
|
+
constructor(create = () => createInflate()) {
|
|
4437
|
+
this.#create = create;
|
|
4438
|
+
}
|
|
4439
|
+
push(data) {
|
|
4440
|
+
if (typeof data === "string")
|
|
4441
|
+
return [data];
|
|
4442
|
+
return this.#inflateFrame(data);
|
|
4443
|
+
}
|
|
4444
|
+
async#inflateFrame(data) {
|
|
4445
|
+
const chunk = await toBytes(data);
|
|
4446
|
+
const inflate = this.#inflate ??= this.#open();
|
|
4447
|
+
if (this.#failure)
|
|
4448
|
+
throw this.#failure;
|
|
4449
|
+
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;
|
|
4450
|
+
await this.#awaiting((settle) => inflate.write(chunk, (error) => error ? settle(error) : settle()));
|
|
4451
|
+
if (!complete)
|
|
4452
|
+
return [];
|
|
4453
|
+
await this.#awaiting((settle) => inflate.flush(zlibConstants.Z_SYNC_FLUSH, () => settle()));
|
|
4454
|
+
const parts = this.#chunks;
|
|
4455
|
+
this.#chunks = [];
|
|
4456
|
+
if (parts.length === 0)
|
|
4457
|
+
return [];
|
|
4458
|
+
const total = parts.reduce((size, part) => size + part.length, 0);
|
|
4459
|
+
const merged = new Uint8Array(total);
|
|
4460
|
+
let offset = 0;
|
|
4461
|
+
for (const part of parts) {
|
|
4462
|
+
merged.set(part, offset);
|
|
4463
|
+
offset += part.length;
|
|
4464
|
+
}
|
|
4465
|
+
return [new TextDecoder().decode(merged)];
|
|
4466
|
+
}
|
|
4467
|
+
reset() {
|
|
4468
|
+
const inflate = this.#inflate;
|
|
4469
|
+
this.#inflate = undefined;
|
|
4470
|
+
this.#chunks = [];
|
|
4471
|
+
this.#failure = undefined;
|
|
4472
|
+
this.#pendingReject = undefined;
|
|
4473
|
+
if (!inflate)
|
|
4474
|
+
return;
|
|
4475
|
+
inflate.removeAllListeners();
|
|
4476
|
+
try {
|
|
4477
|
+
inflate.close();
|
|
4478
|
+
} catch {}
|
|
4479
|
+
}
|
|
4480
|
+
#awaiting(run) {
|
|
4481
|
+
if (this.#failure)
|
|
4482
|
+
return Promise.reject(this.#failure);
|
|
4483
|
+
return new Promise((resolve, reject) => {
|
|
4484
|
+
this.#pendingReject = reject;
|
|
4485
|
+
run((error) => {
|
|
4486
|
+
this.#pendingReject = undefined;
|
|
4487
|
+
if (error)
|
|
4488
|
+
reject(error);
|
|
4489
|
+
else if (this.#failure)
|
|
4490
|
+
reject(this.#failure);
|
|
4491
|
+
else
|
|
4492
|
+
resolve();
|
|
4493
|
+
});
|
|
4494
|
+
});
|
|
4495
|
+
}
|
|
4496
|
+
#open() {
|
|
4497
|
+
const inflate = this.#create();
|
|
4498
|
+
inflate.on("data", (chunk) => this.#chunks.push(chunk));
|
|
4499
|
+
inflate.on("error", (error) => {
|
|
4500
|
+
this.#failure = error;
|
|
4501
|
+
const reject = this.#pendingReject;
|
|
4502
|
+
this.#pendingReject = undefined;
|
|
4503
|
+
reject?.(error);
|
|
4504
|
+
});
|
|
4505
|
+
return inflate;
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
function createDecoder(compress) {
|
|
4509
|
+
return compress ? new ZlibStreamDecoder : new PlainTextDecoder;
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
// packages/ws/src/transport.ts
|
|
4513
|
+
var OPEN = 1;
|
|
4514
|
+
|
|
4515
|
+
class WebSocketTransport {
|
|
4516
|
+
#handlers;
|
|
4517
|
+
#decoder;
|
|
4518
|
+
#createSocket;
|
|
4519
|
+
#compress;
|
|
4520
|
+
#socket;
|
|
4521
|
+
#generation = 0;
|
|
4522
|
+
#queue = Promise.resolve();
|
|
4523
|
+
constructor(options) {
|
|
4524
|
+
this.#handlers = options.handlers;
|
|
4525
|
+
this.#compress = options.compress ?? false;
|
|
4526
|
+
this.#decoder = options.decoder ?? createDecoder(options.compress);
|
|
4527
|
+
this.#createSocket = options.createSocket ?? ((url) => new WebSocket(url));
|
|
4528
|
+
}
|
|
4529
|
+
get connected() {
|
|
4530
|
+
return this.#socket?.readyState === OPEN;
|
|
4531
|
+
}
|
|
4532
|
+
get live() {
|
|
4533
|
+
return this.#socket !== undefined && this.#socket.readyState <= OPEN;
|
|
4534
|
+
}
|
|
4535
|
+
get generation() {
|
|
4536
|
+
return this.#generation;
|
|
4537
|
+
}
|
|
4538
|
+
connect(url) {
|
|
4539
|
+
this.#discard(1000, "Superseded by a new connection");
|
|
4540
|
+
const generation = ++this.#generation;
|
|
4541
|
+
this.#decoder.reset();
|
|
4542
|
+
this.#queue = Promise.resolve();
|
|
4543
|
+
let socket;
|
|
4544
|
+
try {
|
|
4545
|
+
socket = this.#createSocket(url);
|
|
4546
|
+
} catch (error) {
|
|
4547
|
+
return { ok: false, error: this.#asError(error) };
|
|
4548
|
+
}
|
|
4549
|
+
if (this.#compress) {
|
|
4550
|
+
try {
|
|
4551
|
+
socket.binaryType = "arraybuffer";
|
|
4552
|
+
} catch {}
|
|
4553
|
+
}
|
|
4554
|
+
this.#socket = socket;
|
|
4555
|
+
socket.addEventListener("open", () => {
|
|
4556
|
+
if (!this.#owns(generation))
|
|
4557
|
+
return;
|
|
4558
|
+
this.#handlers.onOpen();
|
|
4559
|
+
});
|
|
4560
|
+
socket.addEventListener("message", (event) => {
|
|
4561
|
+
if (!this.#owns(generation))
|
|
4562
|
+
return;
|
|
4563
|
+
let decoded;
|
|
4564
|
+
try {
|
|
4565
|
+
decoded = this.#decoder.push(event.data);
|
|
4566
|
+
} catch (error) {
|
|
4567
|
+
this.#handlers.onError(this.#asError(error));
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4570
|
+
if (!(decoded instanceof Promise)) {
|
|
4571
|
+
for (const frame of decoded)
|
|
4572
|
+
this.#handlers.onFrame(frame);
|
|
4573
|
+
return;
|
|
4574
|
+
}
|
|
4575
|
+
this.#queue = this.#queue.then(async () => {
|
|
4576
|
+
const frames = await decoded;
|
|
4577
|
+
if (!this.#owns(generation))
|
|
4578
|
+
return;
|
|
4579
|
+
for (const frame of frames)
|
|
4580
|
+
this.#handlers.onFrame(frame);
|
|
4581
|
+
}).catch((error) => {
|
|
4582
|
+
if (!this.#owns(generation))
|
|
4583
|
+
return;
|
|
4584
|
+
this.#handlers.onError(this.#asError(error));
|
|
4585
|
+
});
|
|
4586
|
+
});
|
|
4587
|
+
socket.addEventListener("close", (event) => {
|
|
4588
|
+
if (!this.#owns(generation))
|
|
4589
|
+
return;
|
|
4590
|
+
this.#socket = undefined;
|
|
4591
|
+
this.#generation++;
|
|
4592
|
+
this.#decoder.reset();
|
|
4593
|
+
this.#handlers.onClose(event.code, event.reason ?? "");
|
|
4594
|
+
});
|
|
4595
|
+
socket.addEventListener("error", () => {
|
|
4596
|
+
if (!this.#owns(generation))
|
|
4597
|
+
return;
|
|
4598
|
+
this.#handlers.onError(new Error("Gateway WebSocket error"));
|
|
4599
|
+
});
|
|
4600
|
+
return { ok: true };
|
|
4601
|
+
}
|
|
4602
|
+
send(data) {
|
|
4603
|
+
if (!this.connected)
|
|
4604
|
+
return false;
|
|
4605
|
+
try {
|
|
4606
|
+
this.#socket.send(data);
|
|
4607
|
+
return true;
|
|
4608
|
+
} catch (error) {
|
|
4609
|
+
this.#handlers.onError(this.#asError(error));
|
|
4610
|
+
return false;
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
close(code = 1000, reason = "") {
|
|
4614
|
+
const socket = this.#socket;
|
|
4615
|
+
if (!socket)
|
|
4616
|
+
return;
|
|
4617
|
+
try {
|
|
4618
|
+
socket.close(code, reason);
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
this.#handlers.onError(this.#asError(error));
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
destroy(code = 1000, reason = "") {
|
|
4624
|
+
this.#discard(code, reason);
|
|
4625
|
+
this.#decoder.reset();
|
|
3506
4626
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
const messages = await this.fetchMessages(channelId, {
|
|
3510
|
-
...options,
|
|
3511
|
-
limit
|
|
3512
|
-
});
|
|
3513
|
-
return {
|
|
3514
|
-
messages,
|
|
3515
|
-
before: messages[0]?.id,
|
|
3516
|
-
after: messages[messages.length - 1]?.id,
|
|
3517
|
-
hasMore: messages.length === limit
|
|
3518
|
-
};
|
|
4627
|
+
#owns(generation) {
|
|
4628
|
+
return generation === this.#generation;
|
|
3519
4629
|
}
|
|
3520
|
-
|
|
3521
|
-
|
|
4630
|
+
#discard(code, reason) {
|
|
4631
|
+
const socket = this.#socket;
|
|
4632
|
+
if (!socket)
|
|
4633
|
+
return;
|
|
4634
|
+
this.#generation++;
|
|
4635
|
+
this.#socket = undefined;
|
|
4636
|
+
try {
|
|
4637
|
+
socket.close(code, reason);
|
|
4638
|
+
} catch (error) {
|
|
4639
|
+
this.#handlers.onError(this.#asError(error));
|
|
4640
|
+
}
|
|
3522
4641
|
}
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
super.clear();
|
|
4642
|
+
#asError(error) {
|
|
4643
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
3526
4644
|
}
|
|
3527
4645
|
}
|
|
3528
|
-
function isMessageQuery(value) {
|
|
3529
|
-
return typeof value === "object" && value !== null && !(Symbol.iterator in value);
|
|
3530
|
-
}
|
|
3531
4646
|
|
|
3532
|
-
// packages/ws/src/
|
|
4647
|
+
// packages/ws/src/opcodes.ts
|
|
3533
4648
|
var GatewayOpcodes = {
|
|
3534
4649
|
Dispatch: 0,
|
|
3535
4650
|
Heartbeat: 1,
|
|
@@ -3543,33 +4658,142 @@ var GatewayOpcodes = {
|
|
|
3543
4658
|
Hello: 10,
|
|
3544
4659
|
HeartbeatAck: 11
|
|
3545
4660
|
};
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
};
|
|
4661
|
+
|
|
4662
|
+
// packages/ws/src/protocol.ts
|
|
4663
|
+
function classifyFrame(raw) {
|
|
4664
|
+
let payload;
|
|
4665
|
+
try {
|
|
4666
|
+
payload = JSON.parse(raw);
|
|
4667
|
+
} catch (error) {
|
|
4668
|
+
return {
|
|
4669
|
+
sequence: null,
|
|
4670
|
+
action: {
|
|
4671
|
+
type: "invalid",
|
|
4672
|
+
violation: "invalid-json",
|
|
4673
|
+
message: "Gateway returned invalid JSON",
|
|
4674
|
+
cause: error
|
|
4675
|
+
}
|
|
4676
|
+
};
|
|
4677
|
+
}
|
|
4678
|
+
return classifyPayload(payload);
|
|
4679
|
+
}
|
|
4680
|
+
function classifyPayload(payload) {
|
|
4681
|
+
if (payload === null || typeof payload !== "object" || typeof payload.op !== "number")
|
|
4682
|
+
return {
|
|
4683
|
+
sequence: null,
|
|
4684
|
+
action: {
|
|
4685
|
+
type: "invalid",
|
|
4686
|
+
violation: "invalid-payload",
|
|
4687
|
+
message: "Gateway returned an invalid payload"
|
|
4688
|
+
}
|
|
4689
|
+
};
|
|
4690
|
+
const frame = payload;
|
|
4691
|
+
const sequence = typeof frame.s === "number" ? frame.s : null;
|
|
4692
|
+
switch (frame.op) {
|
|
4693
|
+
case GatewayOpcodes.Dispatch:
|
|
4694
|
+
return {
|
|
4695
|
+
sequence,
|
|
4696
|
+
action: {
|
|
4697
|
+
type: "dispatch",
|
|
4698
|
+
event: typeof frame.t === "string" ? frame.t : null,
|
|
4699
|
+
data: frame.d
|
|
4700
|
+
}
|
|
4701
|
+
};
|
|
4702
|
+
case GatewayOpcodes.Hello: {
|
|
4703
|
+
const interval = frame.d?.heartbeat_interval;
|
|
4704
|
+
if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0)
|
|
4705
|
+
return {
|
|
4706
|
+
sequence,
|
|
4707
|
+
action: {
|
|
4708
|
+
type: "invalid",
|
|
4709
|
+
violation: "invalid-hello",
|
|
4710
|
+
message: "Gateway HELLO payload contains an invalid heartbeat interval"
|
|
4711
|
+
}
|
|
4712
|
+
};
|
|
4713
|
+
return {
|
|
4714
|
+
sequence,
|
|
4715
|
+
action: { type: "hello", heartbeatInterval: interval }
|
|
4716
|
+
};
|
|
4717
|
+
}
|
|
4718
|
+
case GatewayOpcodes.Heartbeat:
|
|
4719
|
+
return { sequence, action: { type: "heartbeat" } };
|
|
4720
|
+
case GatewayOpcodes.HeartbeatAck:
|
|
4721
|
+
return {
|
|
4722
|
+
sequence,
|
|
4723
|
+
action: { type: "heartbeat-ack", data: frame.d }
|
|
4724
|
+
};
|
|
4725
|
+
case GatewayOpcodes.Reconnect:
|
|
4726
|
+
return { sequence, action: { type: "reconnect" } };
|
|
4727
|
+
case GatewayOpcodes.InvalidSession:
|
|
4728
|
+
return {
|
|
4729
|
+
sequence,
|
|
4730
|
+
action: {
|
|
4731
|
+
type: "invalid-session",
|
|
4732
|
+
resumable: frame.d === true
|
|
4733
|
+
}
|
|
4734
|
+
};
|
|
4735
|
+
default:
|
|
4736
|
+
return { sequence, action: { type: "unknown", opcode: frame.op } };
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
function identifyPayload(options) {
|
|
4740
|
+
const props = options.properties ?? {};
|
|
4741
|
+
const os = props.os ?? "Android";
|
|
4742
|
+
const browser = props.browser ?? "Discord Android";
|
|
4743
|
+
const device = props.device ?? "Discord Android";
|
|
4744
|
+
return {
|
|
4745
|
+
op: GatewayOpcodes.Identify,
|
|
4746
|
+
d: {
|
|
4747
|
+
token: options.token,
|
|
4748
|
+
intents: options.intents,
|
|
4749
|
+
properties: {
|
|
4750
|
+
os,
|
|
4751
|
+
browser,
|
|
4752
|
+
device,
|
|
4753
|
+
$os: os,
|
|
4754
|
+
$browser: browser,
|
|
4755
|
+
$device: device,
|
|
4756
|
+
...props
|
|
4757
|
+
},
|
|
4758
|
+
presence: {
|
|
4759
|
+
since: options.presence?.since ?? null,
|
|
4760
|
+
activities: options.presence?.activities ?? [],
|
|
4761
|
+
status: options.presence?.status ?? "online",
|
|
4762
|
+
afk: Boolean(options.presence?.afk)
|
|
4763
|
+
},
|
|
4764
|
+
shard: [options.shardId, options.shardCount]
|
|
4765
|
+
},
|
|
4766
|
+
s: null,
|
|
4767
|
+
t: null
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4770
|
+
function resumePayload(token, resume) {
|
|
4771
|
+
return {
|
|
4772
|
+
op: GatewayOpcodes.Resume,
|
|
4773
|
+
d: {
|
|
4774
|
+
token,
|
|
4775
|
+
session_id: resume.sessionId,
|
|
4776
|
+
seq: resume.sequence
|
|
4777
|
+
},
|
|
4778
|
+
s: null,
|
|
4779
|
+
t: null
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
function heartbeatPayload(sequence) {
|
|
4783
|
+
return { op: GatewayOpcodes.Heartbeat, d: sequence, s: null, t: null };
|
|
4784
|
+
}
|
|
4785
|
+
// packages/ws/src/state.ts
|
|
3562
4786
|
var GatewayState;
|
|
3563
|
-
((
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
4787
|
+
((GatewayState) => {
|
|
4788
|
+
GatewayState["Connect"] = "CONNECT";
|
|
4789
|
+
GatewayState["Hello"] = "HELLO";
|
|
4790
|
+
GatewayState["Identify"] = "IDENTIFY";
|
|
4791
|
+
GatewayState["Resume"] = "RESUME";
|
|
4792
|
+
GatewayState["Ready"] = "READY";
|
|
4793
|
+
GatewayState["Dispatch"] = "DISPATCH";
|
|
4794
|
+
GatewayState["Heartbeat"] = "HEARTBEAT";
|
|
4795
|
+
GatewayState["Reconnect"] = "RECONNECT";
|
|
4796
|
+
GatewayState["Closed"] = "CLOSED";
|
|
3573
4797
|
})(GatewayState ||= {});
|
|
3574
4798
|
class GatewayError extends Error {
|
|
3575
4799
|
code;
|
|
@@ -3579,33 +4803,43 @@ class GatewayError extends Error {
|
|
|
3579
4803
|
this.code = code;
|
|
3580
4804
|
}
|
|
3581
4805
|
}
|
|
4806
|
+
// packages/ws/src/send-budget.ts
|
|
4807
|
+
class SendBudget {
|
|
4808
|
+
#timestamps = [];
|
|
4809
|
+
#limit;
|
|
4810
|
+
#windowMs;
|
|
4811
|
+
constructor(limit = 115, windowMs = 60000) {
|
|
4812
|
+
this.#limit = limit;
|
|
4813
|
+
this.#windowMs = windowMs;
|
|
4814
|
+
}
|
|
4815
|
+
get used() {
|
|
4816
|
+
return this.#timestamps.length;
|
|
4817
|
+
}
|
|
4818
|
+
allows(privileged, now = Date.now()) {
|
|
4819
|
+
while (this.#timestamps.length && now - this.#timestamps[0] >= this.#windowMs)
|
|
4820
|
+
this.#timestamps.shift();
|
|
4821
|
+
return privileged || this.#timestamps.length < this.#limit;
|
|
4822
|
+
}
|
|
4823
|
+
record(now = Date.now()) {
|
|
4824
|
+
this.#timestamps.push(now);
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
|
|
4828
|
+
// packages/ws/src/index.ts
|
|
4829
|
+
var DEFAULT_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
|
|
3582
4830
|
|
|
3583
4831
|
class Gateway {
|
|
3584
4832
|
state = "CONNECT" /* Connect */;
|
|
3585
4833
|
#options;
|
|
3586
|
-
#
|
|
3587
|
-
#
|
|
3588
|
-
#
|
|
3589
|
-
#
|
|
3590
|
-
#heartbeatTimer;
|
|
3591
|
-
#initialHeartbeat;
|
|
3592
|
-
#heartbeatAckTimer;
|
|
3593
|
-
#heartbeatACK = true;
|
|
3594
|
-
#heartbeatInterval = 0;
|
|
3595
|
-
#heartbeatSentAt = 0;
|
|
3596
|
-
#lastMessageAt = 0;
|
|
3597
|
-
#zombieTimer;
|
|
3598
|
-
#zombieReported = false;
|
|
4834
|
+
#transport;
|
|
4835
|
+
#session = new GatewaySession;
|
|
4836
|
+
#token = 0;
|
|
4837
|
+
#heartbeat;
|
|
3599
4838
|
#closed = false;
|
|
3600
|
-
#
|
|
3601
|
-
#reconnectTimer;
|
|
3602
|
-
#connectPromise;
|
|
3603
|
-
#resolveConnect;
|
|
3604
|
-
#rejectConnect;
|
|
4839
|
+
#reconnect;
|
|
3605
4840
|
#listeners = new Map;
|
|
3606
|
-
#
|
|
4841
|
+
#sendBudget = new SendBudget;
|
|
3607
4842
|
ping = -1;
|
|
3608
|
-
#decompressor;
|
|
3609
4843
|
constructor(options) {
|
|
3610
4844
|
if (!options.token?.trim())
|
|
3611
4845
|
throw new TypeError("A Gateway token is required.");
|
|
@@ -3632,41 +4866,72 @@ class Gateway {
|
|
|
3632
4866
|
afk: false,
|
|
3633
4867
|
since: null
|
|
3634
4868
|
},
|
|
3635
|
-
...options
|
|
4869
|
+
...options,
|
|
4870
|
+
intents: resolvedIntents
|
|
3636
4871
|
};
|
|
3637
4872
|
if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
|
|
3638
4873
|
throw new RangeError("Gateway shardId must be within the configured shard count.");
|
|
3639
4874
|
if (this.#options.zombieTimeout <= this.#options.heartbeatAckTimeout)
|
|
3640
4875
|
throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
|
|
4876
|
+
this.#transport = new WebSocketTransport({
|
|
4877
|
+
compress: this.#options.compress,
|
|
4878
|
+
createSocket: options.createSocket,
|
|
4879
|
+
handlers: {
|
|
4880
|
+
onOpen: () => this.#onOpen(),
|
|
4881
|
+
onFrame: (raw) => this.#message(raw, this.#token),
|
|
4882
|
+
onClose: (code) => this.#close(code, this.#token),
|
|
4883
|
+
onError: (error) => this.#emitError(error instanceof GatewayError ? error : new GatewayError(error.message, undefined, {
|
|
4884
|
+
cause: error
|
|
4885
|
+
}))
|
|
4886
|
+
}
|
|
4887
|
+
});
|
|
4888
|
+
this.#reconnect = new GatewayReconnect({
|
|
4889
|
+
enabled: this.#options.reconnect,
|
|
4890
|
+
maxAttempts: this.#options.maxReconnectAttempts,
|
|
4891
|
+
baseDelay: this.#options.reconnectBaseDelay,
|
|
4892
|
+
maxDelay: this.#options.reconnectMaxDelay
|
|
4893
|
+
});
|
|
4894
|
+
this.#heartbeat = new GatewayHeartbeat({
|
|
4895
|
+
ackTimeout: this.#options.heartbeatAckTimeout,
|
|
4896
|
+
zombieTimeout: this.#options.zombieTimeout,
|
|
4897
|
+
send: (sequence) => this.#dispatch(heartbeatPayload(sequence), true),
|
|
4898
|
+
sequence: () => this.#session.sequence,
|
|
4899
|
+
isConnected: () => !this.#closed && this.#transport.connected,
|
|
4900
|
+
onTimeout: (timeout) => this.#onHeartbeatTimeout(timeout),
|
|
4901
|
+
onError: (error) => this.#emitError(error)
|
|
4902
|
+
});
|
|
4903
|
+
}
|
|
4904
|
+
#onHeartbeatTimeout(timeout) {
|
|
4905
|
+
if (timeout.type === "zombie") {
|
|
4906
|
+
this.#emit("zombie", {
|
|
4907
|
+
silentFor: timeout.silentFor,
|
|
4908
|
+
timeout: timeout.deadline
|
|
4909
|
+
});
|
|
4910
|
+
this.#emitError(new GatewayError(`Gateway connection appears stale after ${timeout.silentFor}ms without traffic.`));
|
|
4911
|
+
} else {
|
|
4912
|
+
this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${timeout.elapsedMs}ms.`));
|
|
4913
|
+
}
|
|
4914
|
+
this.#transport.close(1001, timeout.type === "zombie" ? "Zombie Gateway connection" : "Heartbeat timeout");
|
|
3641
4915
|
}
|
|
3642
|
-
connect(url =
|
|
4916
|
+
connect(url = DEFAULT_GATEWAY_URL) {
|
|
3643
4917
|
if (this.state === "CLOSED" /* Closed */)
|
|
3644
4918
|
throw new GatewayError("Gateway has been permanently closed.");
|
|
3645
|
-
|
|
3646
|
-
|
|
4919
|
+
const inFlight = this.#reconnect.attemptPromise;
|
|
4920
|
+
if (inFlight)
|
|
4921
|
+
return inFlight;
|
|
4922
|
+
if (this.#transport.live)
|
|
4923
|
+
return Promise.resolve();
|
|
4924
|
+
this.#reconnect.cancel();
|
|
3647
4925
|
this.#closed = false;
|
|
3648
4926
|
this.#setState("CONNECT" /* Connect */);
|
|
3649
4927
|
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
|
|
3650
|
-
|
|
3651
|
-
this.#initDecompressor();
|
|
3652
|
-
this.#connectPromise = new Promise((resolve, reject) => {
|
|
3653
|
-
this.#resolveConnect = resolve;
|
|
3654
|
-
this.#rejectConnect = reject;
|
|
3655
|
-
this.#open(connectURL);
|
|
3656
|
-
});
|
|
3657
|
-
return this.#connectPromise;
|
|
4928
|
+
return this.#reconnect.attempt(() => this.#open(connectURL));
|
|
3658
4929
|
}
|
|
3659
4930
|
close() {
|
|
3660
4931
|
this.#closed = true;
|
|
3661
4932
|
this.#clearTimers();
|
|
3662
4933
|
this.#settleConnect(new GatewayError("Gateway connection closed before socket open."));
|
|
3663
|
-
|
|
3664
|
-
this.#ws = undefined;
|
|
3665
|
-
try {
|
|
3666
|
-
ws?.close(1000, "Client closed connection");
|
|
3667
|
-
} catch (error) {
|
|
3668
|
-
this.#emitError(error);
|
|
3669
|
-
}
|
|
4934
|
+
this.#transport.destroy(1000, "Client closed connection");
|
|
3670
4935
|
this.#setState("CLOSED" /* Closed */);
|
|
3671
4936
|
}
|
|
3672
4937
|
emit(event, data) {
|
|
@@ -3691,23 +4956,17 @@ class Gateway {
|
|
|
3691
4956
|
return this.#dispatch(payload, false);
|
|
3692
4957
|
}
|
|
3693
4958
|
#dispatch(payload, privileged) {
|
|
3694
|
-
if (this.#
|
|
4959
|
+
if (!this.#transport.connected)
|
|
3695
4960
|
return false;
|
|
3696
4961
|
const now = Date.now();
|
|
3697
|
-
|
|
3698
|
-
this.#sendTimestamps.shift();
|
|
3699
|
-
if (!privileged && this.#sendTimestamps.length >= 115) {
|
|
4962
|
+
if (!this.#sendBudget.allows(privileged, now)) {
|
|
3700
4963
|
this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
|
|
3701
4964
|
return false;
|
|
3702
4965
|
}
|
|
3703
|
-
|
|
3704
|
-
this.#ws.send(JSON.stringify(payload));
|
|
3705
|
-
this.#sendTimestamps.push(now);
|
|
3706
|
-
return true;
|
|
3707
|
-
} catch (error) {
|
|
3708
|
-
this.#emitError(error);
|
|
4966
|
+
if (!this.#transport.send(JSON.stringify(payload)))
|
|
3709
4967
|
return false;
|
|
3710
|
-
|
|
4968
|
+
this.#sendBudget.record(now);
|
|
4969
|
+
return true;
|
|
3711
4970
|
}
|
|
3712
4971
|
setPresence(data) {
|
|
3713
4972
|
this.#options.presence = { ...this.#options.presence, ...data };
|
|
@@ -3743,127 +5002,80 @@ class Gateway {
|
|
|
3743
5002
|
#open(url) {
|
|
3744
5003
|
if (this.#closed)
|
|
3745
5004
|
return;
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
} catch (error) {
|
|
3750
|
-
const failure = this.#normalizeError(error);
|
|
3751
|
-
this.#emitError(failure);
|
|
3752
|
-
if (this.#options.reconnect) {
|
|
3753
|
-
this.#setState("RECONNECT" /* Reconnect */);
|
|
3754
|
-
this.#scheduleReconnect();
|
|
3755
|
-
} else
|
|
3756
|
-
this.#settleConnect(failure);
|
|
5005
|
+
this.#token = this.#session.beginConnection();
|
|
5006
|
+
const result = this.#transport.connect(url);
|
|
5007
|
+
if (result.ok)
|
|
3757
5008
|
return;
|
|
5009
|
+
const failure = this.#normalizeError(result.error);
|
|
5010
|
+
this.#emitError(failure);
|
|
5011
|
+
if (this.#options.reconnect) {
|
|
5012
|
+
this.#setState("RECONNECT" /* Reconnect */);
|
|
5013
|
+
this.#scheduleReconnect();
|
|
5014
|
+
} else {
|
|
5015
|
+
this.#settleConnect(failure);
|
|
3758
5016
|
}
|
|
3759
|
-
this.#ws = ws;
|
|
3760
|
-
ws.addEventListener("open", () => {
|
|
3761
|
-
this.#lastMessageAt = Date.now();
|
|
3762
|
-
this.#zombieReported = false;
|
|
3763
|
-
this.#startZombieDetection();
|
|
3764
|
-
this.#emit("open", undefined);
|
|
3765
|
-
this.#settleConnect();
|
|
3766
|
-
});
|
|
3767
|
-
ws.addEventListener("message", (event) => {
|
|
3768
|
-
this.#lastMessageAt = Date.now();
|
|
3769
|
-
this.#zombieReported = false;
|
|
3770
|
-
if (this.#options.compress && event.data instanceof ArrayBuffer) {
|
|
3771
|
-
this.#decompress(new Uint8Array(event.data)).then((text) => this.#message(ws, text)).catch((err) => this.#emitError(err));
|
|
3772
|
-
} else {
|
|
3773
|
-
this.#message(ws, String(event.data));
|
|
3774
|
-
}
|
|
3775
|
-
});
|
|
3776
|
-
ws.addEventListener("close", (event) => this.#close(ws, event.code));
|
|
3777
|
-
ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
|
|
3778
|
-
}
|
|
3779
|
-
#startZombieDetection() {
|
|
3780
|
-
if (this.#zombieTimer)
|
|
3781
|
-
clearInterval(this.#zombieTimer);
|
|
3782
|
-
const interval = Math.max(1, Math.min(1000, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
|
|
3783
|
-
this.#zombieTimer = setInterval(() => {
|
|
3784
|
-
if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
|
|
3785
|
-
return;
|
|
3786
|
-
const silentFor = Date.now() - this.#lastMessageAt;
|
|
3787
|
-
const deadline = Math.max(this.#options.zombieTimeout, this.#heartbeatInterval + this.#options.heartbeatAckTimeout);
|
|
3788
|
-
if (silentFor < deadline || this.#zombieReported)
|
|
3789
|
-
return;
|
|
3790
|
-
this.#zombieReported = true;
|
|
3791
|
-
const error = new GatewayError(`Gateway connection appears stale after ${silentFor}ms without traffic.`);
|
|
3792
|
-
this.#emit("zombie", { silentFor, timeout: deadline });
|
|
3793
|
-
this.#emitError(error);
|
|
3794
|
-
try {
|
|
3795
|
-
this.#ws.close(1001, "Zombie Gateway connection");
|
|
3796
|
-
} catch (closeError) {
|
|
3797
|
-
this.#emitError(closeError);
|
|
3798
|
-
}
|
|
3799
|
-
}, interval);
|
|
3800
5017
|
}
|
|
3801
|
-
#
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
}));
|
|
3809
|
-
ws.close(1002, "Invalid JSON");
|
|
3810
|
-
return;
|
|
3811
|
-
}
|
|
3812
|
-
if (!payload || typeof payload.op !== "number") {
|
|
3813
|
-
this.#emitError(new GatewayError("Gateway returned an invalid payload"));
|
|
3814
|
-
ws.close(1002, "Invalid payload");
|
|
5018
|
+
#onOpen() {
|
|
5019
|
+
this.#heartbeat.watch();
|
|
5020
|
+
this.#emit("open", undefined);
|
|
5021
|
+
this.#settleConnect();
|
|
5022
|
+
}
|
|
5023
|
+
#message(raw, token) {
|
|
5024
|
+
if (!this.#session.owns(token))
|
|
3815
5025
|
return;
|
|
3816
|
-
}
|
|
3817
|
-
if (
|
|
3818
|
-
this.#sequence
|
|
3819
|
-
|
|
3820
|
-
|
|
5026
|
+
const { sequence, action } = classifyFrame(raw);
|
|
5027
|
+
if (sequence !== null)
|
|
5028
|
+
this.#session.recordSequence(sequence, token);
|
|
5029
|
+
this.#apply(action, token);
|
|
5030
|
+
}
|
|
5031
|
+
#apply(action, token) {
|
|
5032
|
+
switch (action.type) {
|
|
5033
|
+
case "dispatch":
|
|
3821
5034
|
this.#setState("DISPATCH" /* Dispatch */);
|
|
3822
|
-
this.#handleDispatch(
|
|
3823
|
-
|
|
3824
|
-
case
|
|
3825
|
-
this.#handleHello(
|
|
3826
|
-
|
|
3827
|
-
case
|
|
5035
|
+
this.#handleDispatch(action.event, action.data, token);
|
|
5036
|
+
return;
|
|
5037
|
+
case "hello":
|
|
5038
|
+
this.#handleHello(action.heartbeatInterval);
|
|
5039
|
+
return;
|
|
5040
|
+
case "heartbeat":
|
|
3828
5041
|
this.#setState("HEARTBEAT" /* Heartbeat */);
|
|
3829
|
-
this.#sendHeartbeat();
|
|
3830
|
-
|
|
3831
|
-
case
|
|
3832
|
-
this.#
|
|
3833
|
-
this.ping =
|
|
3834
|
-
this.#
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
this.#resumeURL = undefined;
|
|
3846
|
-
}
|
|
3847
|
-
this.#emit("invalidSession", resumable);
|
|
3848
|
-
ws.close(resumable ? GatewayCloseCodes.UnknownError : 1000, resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
3849
|
-
break;
|
|
5042
|
+
this.#heartbeat.sendHeartbeat();
|
|
5043
|
+
return;
|
|
5044
|
+
case "heartbeat-ack":
|
|
5045
|
+
this.#heartbeat.acknowledge();
|
|
5046
|
+
this.ping = this.#heartbeat.latency;
|
|
5047
|
+
this.#emit("heartbeatAck", action.data);
|
|
5048
|
+
return;
|
|
5049
|
+
case "reconnect":
|
|
5050
|
+
this.#transport.close(1001, "Server requested reconnect");
|
|
5051
|
+
return;
|
|
5052
|
+
case "invalid-session": {
|
|
5053
|
+
if (!action.resumable)
|
|
5054
|
+
this.#session.invalidate(token, "invalid-session");
|
|
5055
|
+
this.#emit("invalidSession", action.resumable);
|
|
5056
|
+
this.#transport.close(action.resumable ? GatewayCloseCodes.UnknownError : 1000, action.resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
5057
|
+
return;
|
|
3850
5058
|
}
|
|
5059
|
+
case "invalid":
|
|
5060
|
+
this.#emitError(new GatewayError(action.message, undefined, action.cause === undefined ? undefined : { cause: action.cause }));
|
|
5061
|
+
this.#transport.close(1002, action.violation === "invalid-json" ? "Invalid JSON" : action.violation === "invalid-hello" ? "Invalid heartbeat interval" : "Invalid payload");
|
|
5062
|
+
return;
|
|
5063
|
+
case "unknown":
|
|
5064
|
+
return;
|
|
3851
5065
|
}
|
|
3852
5066
|
}
|
|
3853
|
-
#handleDispatch(event, data) {
|
|
5067
|
+
#handleDispatch(event, data, token) {
|
|
3854
5068
|
if (event === "READY") {
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
5069
|
+
if (!this.#session.activate(data, token)) {
|
|
5070
|
+
if (this.#session.owns(token))
|
|
5071
|
+
this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
|
|
3858
5072
|
return;
|
|
3859
5073
|
}
|
|
3860
|
-
this.#
|
|
3861
|
-
this.#resumeURL = ready.resume_gateway_url;
|
|
3862
|
-
this.#attempt = 0;
|
|
5074
|
+
this.#reconnect.reset();
|
|
3863
5075
|
this.#setState("READY" /* Ready */);
|
|
3864
5076
|
this.#emit("ready", data);
|
|
3865
5077
|
} else if (event === "RESUMED") {
|
|
3866
|
-
this.#
|
|
5078
|
+
this.#reconnect.reset();
|
|
3867
5079
|
this.#setState("READY" /* Ready */);
|
|
3868
5080
|
this.#emit("resumed", data);
|
|
3869
5081
|
}
|
|
@@ -3872,183 +5084,60 @@ class Gateway {
|
|
|
3872
5084
|
return;
|
|
3873
5085
|
this.#emit(event ?? "dispatch", data);
|
|
3874
5086
|
}
|
|
3875
|
-
#handleHello(
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
this.#emitError(new GatewayError("Gateway HELLO payload contains an invalid heartbeat interval"));
|
|
3879
|
-
this.#ws?.close(1002, "Invalid heartbeat interval");
|
|
3880
|
-
return;
|
|
3881
|
-
}
|
|
3882
|
-
this.#heartbeatInterval = interval;
|
|
3883
|
-
this.#setState(this.#sessionId && this.#sequence !== null && this.#resumeURL ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
3884
|
-
this.#startHeartbeat(interval);
|
|
5087
|
+
#handleHello(interval) {
|
|
5088
|
+
this.#setState(this.#session.canResume ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
5089
|
+
this.#heartbeat.start(interval);
|
|
3885
5090
|
this.#identifyOrResume();
|
|
3886
5091
|
}
|
|
3887
5092
|
#identifyOrResume() {
|
|
3888
|
-
|
|
5093
|
+
const intent = this.#session.handshake();
|
|
5094
|
+
if (intent.type === "resume") {
|
|
3889
5095
|
this.#setState("RESUME" /* Resume */);
|
|
3890
|
-
this.
|
|
3891
|
-
op: GatewayOpcodes.Resume,
|
|
3892
|
-
d: {
|
|
3893
|
-
token: this.#options.token,
|
|
3894
|
-
session_id: this.#sessionId,
|
|
3895
|
-
seq: this.#sequence
|
|
3896
|
-
},
|
|
3897
|
-
s: null,
|
|
3898
|
-
t: null
|
|
3899
|
-
});
|
|
5096
|
+
this.#dispatch(resumePayload(this.#options.token, intent), true);
|
|
3900
5097
|
return;
|
|
3901
5098
|
}
|
|
3902
5099
|
this.#setState("IDENTIFY" /* Identify */);
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
this.send({
|
|
3912
|
-
op: GatewayOpcodes.Identify,
|
|
3913
|
-
d: {
|
|
3914
|
-
token: this.#options.token,
|
|
3915
|
-
intents: this.#options.intents,
|
|
3916
|
-
properties: {
|
|
3917
|
-
os,
|
|
3918
|
-
browser,
|
|
3919
|
-
device,
|
|
3920
|
-
$os: os,
|
|
3921
|
-
$browser: browser,
|
|
3922
|
-
$device: device,
|
|
3923
|
-
...props
|
|
3924
|
-
},
|
|
3925
|
-
presence: {
|
|
3926
|
-
since: this.#options.presence?.since ?? null,
|
|
3927
|
-
activities: this.#options.presence?.activities ?? [],
|
|
3928
|
-
status: this.#options.presence?.status ?? "online",
|
|
3929
|
-
afk: Boolean(this.#options.presence?.afk)
|
|
3930
|
-
},
|
|
3931
|
-
shard: [this.#options.shardId, this.#options.shardCount]
|
|
3932
|
-
},
|
|
3933
|
-
s: null,
|
|
3934
|
-
t: null
|
|
3935
|
-
});
|
|
3936
|
-
}
|
|
3937
|
-
#startHeartbeat(interval) {
|
|
3938
|
-
this.#clearHeartbeatTimers();
|
|
3939
|
-
this.#heartbeatACK = true;
|
|
3940
|
-
this.#initialHeartbeat = setTimeout(() => this.#sendHeartbeat(), Math.random() * interval);
|
|
3941
|
-
this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), interval);
|
|
3942
|
-
}
|
|
3943
|
-
#sendHeartbeat() {
|
|
3944
|
-
this.#heartbeatACK = false;
|
|
3945
|
-
this.#heartbeatSentAt = Date.now();
|
|
3946
|
-
if (!this.#dispatch({
|
|
3947
|
-
op: GatewayOpcodes.Heartbeat,
|
|
3948
|
-
d: this.#sequence,
|
|
3949
|
-
s: null,
|
|
3950
|
-
t: null
|
|
3951
|
-
}, true)) {
|
|
3952
|
-
this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
3953
|
-
return;
|
|
3954
|
-
}
|
|
3955
|
-
this.#clearHeartbeatAckTimer();
|
|
3956
|
-
this.#heartbeatAckTimer = setTimeout(() => {
|
|
3957
|
-
if (!this.#heartbeatACK) {
|
|
3958
|
-
const elapsed = Date.now() - this.#heartbeatSentAt;
|
|
3959
|
-
this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${elapsed}ms.`));
|
|
3960
|
-
try {
|
|
3961
|
-
this.#ws?.close(1001, "Heartbeat timeout");
|
|
3962
|
-
} catch (error) {
|
|
3963
|
-
this.#emitError(error);
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
}, this.#options.heartbeatAckTimeout);
|
|
5100
|
+
this.#dispatch(identifyPayload({
|
|
5101
|
+
token: this.#options.token,
|
|
5102
|
+
intents: this.#options.intents,
|
|
5103
|
+
shardId: this.#options.shardId,
|
|
5104
|
+
shardCount: this.#options.shardCount,
|
|
5105
|
+
properties: this.#options.properties,
|
|
5106
|
+
presence: this.#options.presence
|
|
5107
|
+
}), true);
|
|
3967
5108
|
}
|
|
3968
|
-
#close(
|
|
3969
|
-
if (this.#ws !== ws)
|
|
3970
|
-
return;
|
|
3971
|
-
this.#ws = undefined;
|
|
5109
|
+
#close(code, token) {
|
|
3972
5110
|
this.#clearTimers();
|
|
3973
|
-
const action = this.#
|
|
5111
|
+
const action = this.#reconnect.classifyClose(code, this.#session.canResume);
|
|
3974
5112
|
this.#emit("close", { code, action });
|
|
3975
|
-
if (this.#closed || !this.#
|
|
5113
|
+
if (this.#closed || !this.#reconnect.enabled || action === "stop") {
|
|
5114
|
+
if (action === "stop")
|
|
5115
|
+
this.#closed = true;
|
|
3976
5116
|
this.#setState(this.#closed ? "CLOSED" /* Closed */ : "CONNECT" /* Connect */);
|
|
3977
|
-
if (this.#
|
|
3978
|
-
this.#
|
|
5117
|
+
if (this.#reconnect.connecting)
|
|
5118
|
+
this.#reconnect.settle(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
|
|
3979
5119
|
return;
|
|
3980
5120
|
}
|
|
3981
|
-
if (action === "identify")
|
|
3982
|
-
this.#
|
|
3983
|
-
this.#sessionId = undefined;
|
|
3984
|
-
this.#resumeURL = undefined;
|
|
3985
|
-
}
|
|
5121
|
+
if (action === "identify")
|
|
5122
|
+
this.#session.invalidate(token, "session-timeout");
|
|
3986
5123
|
this.#setState("RECONNECT" /* Reconnect */);
|
|
3987
5124
|
this.#scheduleReconnect();
|
|
3988
5125
|
}
|
|
3989
|
-
#closeAction(code) {
|
|
3990
|
-
const fatal = [
|
|
3991
|
-
GatewayCloseCodes.AuthenticationFailed,
|
|
3992
|
-
GatewayCloseCodes.InvalidShard,
|
|
3993
|
-
GatewayCloseCodes.ShardingRequired,
|
|
3994
|
-
GatewayCloseCodes.InvalidAPIVersion,
|
|
3995
|
-
GatewayCloseCodes.InvalidIntents,
|
|
3996
|
-
GatewayCloseCodes.DisallowedIntents
|
|
3997
|
-
];
|
|
3998
|
-
if (fatal.includes(code))
|
|
3999
|
-
return "stop";
|
|
4000
|
-
if (code === GatewayCloseCodes.InvalidSeq || code === GatewayCloseCodes.SessionTimedOut)
|
|
4001
|
-
return "identify";
|
|
4002
|
-
return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
|
|
4003
|
-
}
|
|
4004
5126
|
#scheduleReconnect() {
|
|
4005
|
-
if (this.#closed
|
|
5127
|
+
if (this.#closed)
|
|
4006
5128
|
return;
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
this.#setState("CONNECT" /* Connect */);
|
|
5129
|
+
const result = this.#reconnect.schedule(() => this.#open(this.#session.connectURL(DEFAULT_GATEWAY_URL)));
|
|
5130
|
+
if (result.scheduled || result.reason !== "exhausted")
|
|
4010
5131
|
return;
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
const jitter = Math.random() * Math.max(1, delay * 0.25);
|
|
4014
|
-
this.#reconnectTimer = setTimeout(() => {
|
|
4015
|
-
this.#reconnectTimer = undefined;
|
|
4016
|
-
this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
|
|
4017
|
-
}, delay + jitter);
|
|
5132
|
+
this.#reconnect.settle(new GatewayError("Gateway reconnect attempts exhausted."));
|
|
5133
|
+
this.#setState("CONNECT" /* Connect */);
|
|
4018
5134
|
}
|
|
4019
5135
|
#settleConnect(error) {
|
|
4020
|
-
|
|
4021
|
-
const reject = this.#rejectConnect;
|
|
4022
|
-
this.#resolveConnect = undefined;
|
|
4023
|
-
this.#rejectConnect = undefined;
|
|
4024
|
-
this.#connectPromise = undefined;
|
|
4025
|
-
if (error)
|
|
4026
|
-
reject?.(error);
|
|
4027
|
-
else
|
|
4028
|
-
resolve?.();
|
|
4029
|
-
}
|
|
4030
|
-
#clearHeartbeatAckTimer() {
|
|
4031
|
-
if (this.#heartbeatAckTimer)
|
|
4032
|
-
clearTimeout(this.#heartbeatAckTimer);
|
|
4033
|
-
this.#heartbeatAckTimer = undefined;
|
|
4034
|
-
}
|
|
4035
|
-
#clearHeartbeatTimers() {
|
|
4036
|
-
if (this.#heartbeatTimer)
|
|
4037
|
-
clearInterval(this.#heartbeatTimer);
|
|
4038
|
-
if (this.#initialHeartbeat)
|
|
4039
|
-
clearTimeout(this.#initialHeartbeat);
|
|
4040
|
-
this.#clearHeartbeatAckTimer();
|
|
4041
|
-
this.#heartbeatTimer = undefined;
|
|
4042
|
-
this.#initialHeartbeat = undefined;
|
|
5136
|
+
this.#reconnect.settle(error);
|
|
4043
5137
|
}
|
|
4044
5138
|
#clearTimers() {
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
if (this.#zombieTimer)
|
|
4048
|
-
clearInterval(this.#zombieTimer);
|
|
4049
|
-
this.#reconnectTimer = undefined;
|
|
4050
|
-
this.#zombieTimer = undefined;
|
|
4051
|
-
this.#clearHeartbeatTimers();
|
|
5139
|
+
this.#reconnect.cancel();
|
|
5140
|
+
this.#heartbeat.stop();
|
|
4052
5141
|
}
|
|
4053
5142
|
#normalizeError(error) {
|
|
4054
5143
|
return error instanceof GatewayError ? error : new GatewayError("Gateway connection failed.", undefined, {
|
|
@@ -4080,37 +5169,6 @@ class Gateway {
|
|
|
4080
5169
|
this.state = next;
|
|
4081
5170
|
this.#emit("stateChange", { previous, next });
|
|
4082
5171
|
}
|
|
4083
|
-
#initDecompressor() {
|
|
4084
|
-
const ds = new DecompressionStream("deflate-raw");
|
|
4085
|
-
this.#decompressor = {
|
|
4086
|
-
writer: ds.writable.getWriter(),
|
|
4087
|
-
reader: ds.readable.getReader()
|
|
4088
|
-
};
|
|
4089
|
-
}
|
|
4090
|
-
async#decompress(chunk) {
|
|
4091
|
-
if (!this.#decompressor)
|
|
4092
|
-
this.#initDecompressor();
|
|
4093
|
-
const { writer, reader } = this.#decompressor;
|
|
4094
|
-
await writer.write(chunk);
|
|
4095
|
-
const parts = [];
|
|
4096
|
-
while (true) {
|
|
4097
|
-
const { done, value } = await Promise.race([
|
|
4098
|
-
reader.read(),
|
|
4099
|
-
new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), 50))
|
|
4100
|
-
]);
|
|
4101
|
-
if (done || !value)
|
|
4102
|
-
break;
|
|
4103
|
-
parts.push(value);
|
|
4104
|
-
}
|
|
4105
|
-
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
4106
|
-
const merged = new Uint8Array(total);
|
|
4107
|
-
let offset = 0;
|
|
4108
|
-
for (const p of parts) {
|
|
4109
|
-
merged.set(p, offset);
|
|
4110
|
-
offset += p.length;
|
|
4111
|
-
}
|
|
4112
|
-
return new TextDecoder().decode(merged);
|
|
4113
|
-
}
|
|
4114
5172
|
}
|
|
4115
5173
|
|
|
4116
5174
|
// packages/core/src/index.ts
|
|
@@ -4181,6 +5239,7 @@ class Client extends EventEmitter2 {
|
|
|
4181
5239
|
users;
|
|
4182
5240
|
guilds;
|
|
4183
5241
|
channels;
|
|
5242
|
+
stageInstances;
|
|
4184
5243
|
application;
|
|
4185
5244
|
get ws() {
|
|
4186
5245
|
return this.#gateway;
|
|
@@ -4223,14 +5282,15 @@ class Client extends EventEmitter2 {
|
|
|
4223
5282
|
this.users = new UserManager(this.rest);
|
|
4224
5283
|
this.guilds = new GuildManager(this.rest);
|
|
4225
5284
|
this.channels = new ChannelManager(this.rest);
|
|
5285
|
+
this.stageInstances = new StageInstanceManager(this.rest);
|
|
4226
5286
|
const placeholderAppCommands = new ApplicationCommandManager(this.rest, "0");
|
|
4227
5287
|
this.application = { commands: placeholderAppCommands };
|
|
4228
5288
|
this.#resourceContext = {
|
|
4229
|
-
sendMessage: (channelId,
|
|
4230
|
-
editMessage: (channelId, messageId,
|
|
5289
|
+
sendMessage: (channelId, options) => this.channels.send(channelId, options),
|
|
5290
|
+
editMessage: (channelId, messageId, options) => this.channels.editMessage(channelId, messageId, options),
|
|
4231
5291
|
deleteMessage: (channelId, messageId) => this.channels.deleteMessage(channelId, messageId),
|
|
4232
5292
|
crosspostMessage: (channelId, messageId) => this.channels.crosspostMessage(channelId, messageId),
|
|
4233
|
-
editChannel: (channelId,
|
|
5293
|
+
editChannel: (channelId, options) => this.channels.edit(channelId, options),
|
|
4234
5294
|
deleteChannel: (channelId) => this.channels.deleteChannel(channelId),
|
|
4235
5295
|
addReaction: (channelId, messageId, emoji) => this.channels.addReaction(channelId, messageId, emoji),
|
|
4236
5296
|
removeOwnReaction: (channelId, messageId, emoji) => this.channels.removeOwnReaction(channelId, messageId, emoji),
|
|
@@ -4302,7 +5362,7 @@ class Client extends EventEmitter2 {
|
|
|
4302
5362
|
...payload.channels ?? [],
|
|
4303
5363
|
...payload.threads ?? []
|
|
4304
5364
|
])
|
|
4305
|
-
this.channels.set(channelData.id,
|
|
5365
|
+
this.channels.set(channelData.id, createChannel(channelData, this.#resourceContext));
|
|
4306
5366
|
this.emit(wasCached ? "guildAvailable" /* GuildAvailable */ : "guildCreate" /* GuildCreate */, payload);
|
|
4307
5367
|
});
|
|
4308
5368
|
this.#gateway.on("GUILD_UPDATE", (data) => {
|
|
@@ -4349,12 +5409,12 @@ class Client extends EventEmitter2 {
|
|
|
4349
5409
|
this.#gateway.on("AUTO_MODERATION_RULE_DELETE", (data) => this.emit("autoModerationRuleDelete" /* AutoModerationRuleDelete */, data));
|
|
4350
5410
|
this.#gateway.on("AUTO_MODERATION_ACTION_EXECUTION", (data) => this.emit("autoModerationActionExecution" /* AutoModerationActionExecution */, data));
|
|
4351
5411
|
this.#gateway.on("CHANNEL_CREATE", (data) => {
|
|
4352
|
-
const channel =
|
|
5412
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
4353
5413
|
this.channels.update(channel);
|
|
4354
5414
|
this.emit("channelCreate" /* ChannelCreate */, channel);
|
|
4355
5415
|
});
|
|
4356
5416
|
this.#gateway.on("CHANNEL_UPDATE", (data) => {
|
|
4357
|
-
const channel =
|
|
5417
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
4358
5418
|
this.channels.update(channel);
|
|
4359
5419
|
this.emit("channelUpdate" /* ChannelUpdate */, channel);
|
|
4360
5420
|
});
|
|
@@ -4365,12 +5425,12 @@ class Client extends EventEmitter2 {
|
|
|
4365
5425
|
});
|
|
4366
5426
|
this.#gateway.on("CHANNEL_PINS_UPDATE", (data) => this.emit("channelPinsUpdate" /* ChannelPinsUpdate */, data));
|
|
4367
5427
|
this.#gateway.on("THREAD_CREATE", (data) => {
|
|
4368
|
-
const channel =
|
|
5428
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
4369
5429
|
this.channels.update(channel);
|
|
4370
5430
|
this.emit("threadCreate" /* ThreadCreate */, channel);
|
|
4371
5431
|
});
|
|
4372
5432
|
this.#gateway.on("THREAD_UPDATE", (data) => {
|
|
4373
|
-
const channel =
|
|
5433
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
4374
5434
|
this.channels.update(channel);
|
|
4375
5435
|
this.emit("threadUpdate" /* ThreadUpdate */, channel);
|
|
4376
5436
|
});
|
|
@@ -4450,7 +5510,7 @@ class Client extends EventEmitter2 {
|
|
|
4450
5510
|
if (options?.guildScheduledEventId)
|
|
4451
5511
|
query.set("guild_scheduled_event_id", options.guildScheduledEventId);
|
|
4452
5512
|
const qs = query.toString();
|
|
4453
|
-
return this.rest.get(`/invites/${code}${qs ? `?${qs}` : ""}`);
|
|
5513
|
+
return this.rest.get(`/invites/${encodeURIComponent(code)}${qs ? `?${qs}` : ""}`);
|
|
4454
5514
|
}
|
|
4455
5515
|
fetchSticker(id) {
|
|
4456
5516
|
return this.rest.get(`/stickers/${id}`);
|
|
@@ -4459,14 +5519,14 @@ class Client extends EventEmitter2 {
|
|
|
4459
5519
|
return this.rest.get(`/sticker-packs`);
|
|
4460
5520
|
}
|
|
4461
5521
|
fetchGuildTemplate(code) {
|
|
4462
|
-
return this.rest.get(`/guilds/templates/${code}`);
|
|
5522
|
+
return this.rest.get(`/guilds/templates/${encodeURIComponent(code)}`);
|
|
4463
5523
|
}
|
|
4464
5524
|
generateInvite(options) {
|
|
4465
5525
|
if (!this.user)
|
|
4466
5526
|
throw new Error("Client must be ready to generate invite.");
|
|
4467
5527
|
const params = new URLSearchParams({
|
|
4468
5528
|
client_id: this.user.id,
|
|
4469
|
-
|
|
5529
|
+
scope: (options?.scopes ?? ["bot"]).join(" ")
|
|
4470
5530
|
});
|
|
4471
5531
|
if (options?.permissions) {
|
|
4472
5532
|
params.set("permissions", String(options.permissions));
|
|
@@ -4588,13 +5648,17 @@ class HandlerRegistry {
|
|
|
4588
5648
|
}
|
|
4589
5649
|
}
|
|
4590
5650
|
// packages/sharding/src/bus.ts
|
|
5651
|
+
var REPLY_TYPE = "__lunibee:reply";
|
|
5652
|
+
|
|
4591
5653
|
class ShardBus {
|
|
4592
5654
|
#channel;
|
|
4593
5655
|
#handlers = new Map;
|
|
5656
|
+
#errorHandlers = new Set;
|
|
4594
5657
|
#shardId;
|
|
4595
5658
|
#namespace;
|
|
4596
5659
|
channelName;
|
|
4597
5660
|
#counter = 0;
|
|
5661
|
+
#pending = new Map;
|
|
4598
5662
|
constructor(shardId, channelName) {
|
|
4599
5663
|
if (!Number.isInteger(shardId) || shardId < 0)
|
|
4600
5664
|
throw new RangeError("Shard ID must be a non-negative integer.");
|
|
@@ -4619,6 +5683,16 @@ class ShardBus {
|
|
|
4619
5683
|
this.#handlers.get(type)?.delete(handler);
|
|
4620
5684
|
return this;
|
|
4621
5685
|
}
|
|
5686
|
+
onError(handler) {
|
|
5687
|
+
if (typeof handler !== "function")
|
|
5688
|
+
throw new TypeError("Shard bus error handler is required.");
|
|
5689
|
+
this.#errorHandlers.add(handler);
|
|
5690
|
+
return this;
|
|
5691
|
+
}
|
|
5692
|
+
offError(handler) {
|
|
5693
|
+
this.#errorHandlers.delete(handler);
|
|
5694
|
+
return this;
|
|
5695
|
+
}
|
|
4622
5696
|
send(target, type, data) {
|
|
4623
5697
|
if (!Number.isInteger(target) || target < 0)
|
|
4624
5698
|
throw new RangeError("Shard target must be a non-negative integer.");
|
|
@@ -4627,11 +5701,56 @@ class ShardBus {
|
|
|
4627
5701
|
broadcast(type, data) {
|
|
4628
5702
|
return this.#publish(null, type, data);
|
|
4629
5703
|
}
|
|
5704
|
+
respond(type, handler) {
|
|
5705
|
+
return this.on(type, async (message) => {
|
|
5706
|
+
if (!message.expectsReply)
|
|
5707
|
+
return;
|
|
5708
|
+
const reply = { requestId: message.id };
|
|
5709
|
+
try {
|
|
5710
|
+
reply.result = await handler(message.data, message);
|
|
5711
|
+
} catch (error) {
|
|
5712
|
+
reply.error = error instanceof Error ? error.message : String(error);
|
|
5713
|
+
}
|
|
5714
|
+
this.#publish(message.source, REPLY_TYPE, reply);
|
|
5715
|
+
});
|
|
5716
|
+
}
|
|
5717
|
+
async request(target, type, data, timeoutMs = 5000) {
|
|
5718
|
+
if (!Number.isInteger(target) || target < 0)
|
|
5719
|
+
throw new RangeError("Shard target must be a non-negative integer.");
|
|
5720
|
+
const [reply] = await this.#collect(target, type, data, timeoutMs, 1);
|
|
5721
|
+
if (!reply)
|
|
5722
|
+
throw new Error(`Shard ${target} did not reply to "${type}" within ${timeoutMs}ms.`);
|
|
5723
|
+
if (reply.error !== undefined)
|
|
5724
|
+
throw new Error(reply.error);
|
|
5725
|
+
return reply.result;
|
|
5726
|
+
}
|
|
5727
|
+
broadcastRequest(type, data, options = {}) {
|
|
5728
|
+
return this.#collect(null, type, data, options.timeoutMs ?? 5000, options.expected ?? Number.POSITIVE_INFINITY);
|
|
5729
|
+
}
|
|
5730
|
+
#collect(target, type, data, timeoutMs, expected) {
|
|
5731
|
+
const replies = [];
|
|
5732
|
+
return new Promise((resolve) => {
|
|
5733
|
+
let requestId = "";
|
|
5734
|
+
const finish = () => {
|
|
5735
|
+
clearTimeout(timer);
|
|
5736
|
+
this.#pending.delete(requestId);
|
|
5737
|
+
resolve(replies);
|
|
5738
|
+
};
|
|
5739
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
5740
|
+
requestId = this.#publish(target, type, data, true);
|
|
5741
|
+
this.#pending.set(requestId, (reply) => {
|
|
5742
|
+
replies.push(reply);
|
|
5743
|
+
if (replies.length >= expected)
|
|
5744
|
+
finish();
|
|
5745
|
+
});
|
|
5746
|
+
});
|
|
5747
|
+
}
|
|
4630
5748
|
close() {
|
|
4631
5749
|
this.#channel.close();
|
|
4632
5750
|
this.#handlers.clear();
|
|
5751
|
+
this.#errorHandlers.clear();
|
|
4633
5752
|
}
|
|
4634
|
-
#publish(target, type, data) {
|
|
5753
|
+
#publish(target, type, data, expectsReply = false) {
|
|
4635
5754
|
if (!type.trim())
|
|
4636
5755
|
throw new TypeError("Shard message type is required.");
|
|
4637
5756
|
const id = `${this.#namespace}:${this.#shardId}:${++this.#counter}`;
|
|
@@ -4640,7 +5759,8 @@ class ShardBus {
|
|
|
4640
5759
|
target,
|
|
4641
5760
|
type,
|
|
4642
5761
|
data,
|
|
4643
|
-
id
|
|
5762
|
+
id,
|
|
5763
|
+
...expectsReply ? { expectsReply } : {}
|
|
4644
5764
|
});
|
|
4645
5765
|
return id;
|
|
4646
5766
|
}
|
|
@@ -4649,13 +5769,28 @@ class ShardBus {
|
|
|
4649
5769
|
return;
|
|
4650
5770
|
if (message.source === this.#shardId || message.target !== null && message.target !== this.#shardId)
|
|
4651
5771
|
return;
|
|
5772
|
+
if (message.type === REPLY_TYPE) {
|
|
5773
|
+
const reply = message.data;
|
|
5774
|
+
this.#pending.get(reply?.requestId)?.({
|
|
5775
|
+
shardId: message.source,
|
|
5776
|
+
...reply.error !== undefined ? { error: reply.error } : { result: reply.result }
|
|
5777
|
+
});
|
|
5778
|
+
return;
|
|
5779
|
+
}
|
|
4652
5780
|
for (const handler of this.#handlers.get(message.type) ?? []) {
|
|
4653
5781
|
try {
|
|
4654
5782
|
const result = handler(message);
|
|
4655
5783
|
if (result && typeof result.then === "function")
|
|
4656
|
-
Promise.resolve(result).catch(() =>
|
|
4657
|
-
|
|
4658
|
-
|
|
5784
|
+
Promise.resolve(result).catch((error) => this.#reportError(error, message));
|
|
5785
|
+
} catch (error) {
|
|
5786
|
+
this.#reportError(error, message);
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
#reportError(error, message) {
|
|
5791
|
+
for (const handler of this.#errorHandlers) {
|
|
5792
|
+
try {
|
|
5793
|
+
handler(error, message);
|
|
4659
5794
|
} catch {}
|
|
4660
5795
|
}
|
|
4661
5796
|
}
|
|
@@ -4663,7 +5798,7 @@ class ShardBus {
|
|
|
4663
5798
|
// packages/sharding/src/cluster.ts
|
|
4664
5799
|
import { fork } from "child_process";
|
|
4665
5800
|
import { cpus } from "os";
|
|
4666
|
-
var
|
|
5801
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4667
5802
|
|
|
4668
5803
|
class ClusterManager {
|
|
4669
5804
|
clusters = new Map;
|
|
@@ -4672,6 +5807,8 @@ class ClusterManager {
|
|
|
4672
5807
|
#auto;
|
|
4673
5808
|
#autoScaleTimer;
|
|
4674
5809
|
#spawned = false;
|
|
5810
|
+
#stopping = false;
|
|
5811
|
+
#restartTimers = new Set;
|
|
4675
5812
|
constructor(options) {
|
|
4676
5813
|
if (!options.token?.trim())
|
|
4677
5814
|
throw new TypeError("A Discord bot token is required.");
|
|
@@ -4716,20 +5853,8 @@ class ClusterManager {
|
|
|
4716
5853
|
for (let i = 0;i < clusterCount; i++) {
|
|
4717
5854
|
if (chunks[i].length === 0)
|
|
4718
5855
|
continue;
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
...process.env,
|
|
4722
|
-
SHARD_LIST: chunks[i].join(","),
|
|
4723
|
-
SHARD_COUNT: count.toString(),
|
|
4724
|
-
CLUSTER_ID: i.toString()
|
|
4725
|
-
}
|
|
4726
|
-
});
|
|
4727
|
-
this.clusters.set(i, {
|
|
4728
|
-
id: i,
|
|
4729
|
-
process: child,
|
|
4730
|
-
shards: chunks[i]
|
|
4731
|
-
});
|
|
4732
|
-
await sleep(500);
|
|
5856
|
+
this.#launch(i, chunks[i], count);
|
|
5857
|
+
await sleep2(500);
|
|
4733
5858
|
}
|
|
4734
5859
|
if (this.#options.autoScaleInterval && this.#options.autoScaleInterval > 0) {
|
|
4735
5860
|
this.#autoScaleTimer = setInterval(() => {
|
|
@@ -4737,6 +5862,37 @@ class ClusterManager {
|
|
|
4737
5862
|
}, this.#options.autoScaleInterval);
|
|
4738
5863
|
}
|
|
4739
5864
|
}
|
|
5865
|
+
#launch(id, shards, shardCount) {
|
|
5866
|
+
const child = fork(this.#options.script, [], {
|
|
5867
|
+
env: {
|
|
5868
|
+
...process.env,
|
|
5869
|
+
SHARD_LIST: shards.join(","),
|
|
5870
|
+
SHARD_COUNT: shardCount.toString(),
|
|
5871
|
+
CLUSTER_ID: id.toString()
|
|
5872
|
+
}
|
|
5873
|
+
});
|
|
5874
|
+
const info = { id, process: child, shards };
|
|
5875
|
+
this.clusters.set(id, info);
|
|
5876
|
+
child.once("exit", (code, signal) => {
|
|
5877
|
+
if (this.clusters.get(id)?.process !== child)
|
|
5878
|
+
return;
|
|
5879
|
+
try {
|
|
5880
|
+
this.#options.onClusterExit?.(info, code, signal);
|
|
5881
|
+
} catch {}
|
|
5882
|
+
if (this.#stopping || this.#options.restartOnExit === false)
|
|
5883
|
+
return;
|
|
5884
|
+
this.clusters.delete(id);
|
|
5885
|
+
const timer = setTimeout(() => {
|
|
5886
|
+
this.#restartTimers.delete(timer);
|
|
5887
|
+
if (this.#stopping || !this.#spawned)
|
|
5888
|
+
return;
|
|
5889
|
+
this.#launch(id, shards, shardCount);
|
|
5890
|
+
}, this.#options.restartDelay ?? 5000);
|
|
5891
|
+
timer.unref?.();
|
|
5892
|
+
this.#restartTimers.add(timer);
|
|
5893
|
+
});
|
|
5894
|
+
return info;
|
|
5895
|
+
}
|
|
4740
5896
|
async checkAutoScale() {
|
|
4741
5897
|
if (!this.#auto)
|
|
4742
5898
|
return;
|
|
@@ -4757,6 +5913,8 @@ class ClusterManager {
|
|
|
4757
5913
|
await this.spawn();
|
|
4758
5914
|
}
|
|
4759
5915
|
async shutdownAll(timeoutMs = this.#options.shutdownTimeout ?? 5000) {
|
|
5916
|
+
this.#stopping = true;
|
|
5917
|
+
this.#clearRestartTimers();
|
|
4760
5918
|
if (this.#autoScaleTimer) {
|
|
4761
5919
|
clearInterval(this.#autoScaleTimer);
|
|
4762
5920
|
this.#autoScaleTimer = undefined;
|
|
@@ -4764,6 +5922,12 @@ class ClusterManager {
|
|
|
4764
5922
|
await Promise.all([...this.clusters.values()].map((cluster) => this.#shutdownCluster(cluster, timeoutMs)));
|
|
4765
5923
|
this.clusters.clear();
|
|
4766
5924
|
this.#spawned = false;
|
|
5925
|
+
this.#stopping = false;
|
|
5926
|
+
}
|
|
5927
|
+
#clearRestartTimers() {
|
|
5928
|
+
for (const timer of this.#restartTimers)
|
|
5929
|
+
clearTimeout(timer);
|
|
5930
|
+
this.#restartTimers.clear();
|
|
4767
5931
|
}
|
|
4768
5932
|
async#shutdownCluster(cluster, timeoutMs) {
|
|
4769
5933
|
const child = cluster.process;
|
|
@@ -4792,6 +5956,8 @@ class ClusterManager {
|
|
|
4792
5956
|
});
|
|
4793
5957
|
}
|
|
4794
5958
|
killAll() {
|
|
5959
|
+
this.#stopping = true;
|
|
5960
|
+
this.#clearRestartTimers();
|
|
4795
5961
|
if (this.#autoScaleTimer) {
|
|
4796
5962
|
clearInterval(this.#autoScaleTimer);
|
|
4797
5963
|
this.#autoScaleTimer = undefined;
|
|
@@ -4803,13 +5969,15 @@ class ClusterManager {
|
|
|
4803
5969
|
}
|
|
4804
5970
|
this.clusters.clear();
|
|
4805
5971
|
this.#spawned = false;
|
|
5972
|
+
this.#stopping = false;
|
|
4806
5973
|
}
|
|
4807
5974
|
}
|
|
4808
5975
|
|
|
4809
5976
|
// packages/sharding/src/index.ts
|
|
4810
|
-
var
|
|
5977
|
+
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4811
5978
|
|
|
4812
5979
|
class ShardManager {
|
|
5980
|
+
static IDENTIFY_INTERVAL = 5000;
|
|
4813
5981
|
shards = new Map;
|
|
4814
5982
|
get shardCount() {
|
|
4815
5983
|
return this.shards.size;
|
|
@@ -4829,8 +5997,13 @@ class ShardManager {
|
|
|
4829
5997
|
throw new RangeError("Shard count must be a positive integer.");
|
|
4830
5998
|
if (options.autoScaleInterval !== undefined && (!Number.isInteger(options.autoScaleInterval) || options.autoScaleInterval < 1000))
|
|
4831
5999
|
throw new RangeError("autoScaleInterval must be an integer of at least 1000 milliseconds.");
|
|
6000
|
+
if (options.spawnDelay !== undefined && (!Number.isFinite(options.spawnDelay) || options.spawnDelay < 0))
|
|
6001
|
+
throw new RangeError("spawnDelay must be a non-negative finite number of milliseconds.");
|
|
4832
6002
|
this.#auto = options.shardCount === "auto";
|
|
4833
|
-
this.#options = {
|
|
6003
|
+
this.#options = {
|
|
6004
|
+
spawnDelay: ShardManager.IDENTIFY_INTERVAL,
|
|
6005
|
+
...options
|
|
6006
|
+
};
|
|
4834
6007
|
if (options.shardCount !== "auto")
|
|
4835
6008
|
this.#initialize(count);
|
|
4836
6009
|
}
|
|
@@ -4864,7 +6037,7 @@ class ShardManager {
|
|
|
4864
6037
|
});
|
|
4865
6038
|
}
|
|
4866
6039
|
if (id + 1 < this.shardCount && this.#options.spawnDelay && this.#options.spawnDelay > 0)
|
|
4867
|
-
await
|
|
6040
|
+
await sleep3(this.#options.spawnDelay);
|
|
4868
6041
|
}
|
|
4869
6042
|
if (this.#options.autoScaleInterval && !this.#autoScaleTimer) {
|
|
4870
6043
|
this.#autoScaleTimer = setInterval(() => {
|
|
@@ -4936,7 +6109,7 @@ class ShardManager {
|
|
|
4936
6109
|
}
|
|
4937
6110
|
}
|
|
4938
6111
|
// packages/utils/src/index.ts
|
|
4939
|
-
function
|
|
6112
|
+
function sleep4(ms) {
|
|
4940
6113
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4941
6114
|
}
|
|
4942
6115
|
function randomInt(min, max) {
|
|
@@ -4947,11 +6120,11 @@ function isSnowflake(value) {
|
|
|
4947
6120
|
}
|
|
4948
6121
|
// packages/voice/src/index.ts
|
|
4949
6122
|
var VoiceConnectionState;
|
|
4950
|
-
((
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
6123
|
+
((VoiceConnectionState) => {
|
|
6124
|
+
VoiceConnectionState["Disconnected"] = "disconnected";
|
|
6125
|
+
VoiceConnectionState["Connecting"] = "connecting";
|
|
6126
|
+
VoiceConnectionState["Connected"] = "connected";
|
|
6127
|
+
VoiceConnectionState["Destroyed"] = "destroyed";
|
|
4955
6128
|
})(VoiceConnectionState ||= {});
|
|
4956
6129
|
|
|
4957
6130
|
class VoiceError extends Error {
|
|
@@ -5293,6 +6466,52 @@ class VoiceReceiver {
|
|
|
5293
6466
|
}
|
|
5294
6467
|
}
|
|
5295
6468
|
}
|
|
6469
|
+
var voiceConnections = new Map;
|
|
6470
|
+
function joinVoiceChannel(options) {
|
|
6471
|
+
let connection = voiceConnections.get(options.guildId);
|
|
6472
|
+
if (!connection) {
|
|
6473
|
+
connection = new VoiceConnection(options);
|
|
6474
|
+
const created = connection;
|
|
6475
|
+
created.on("stateChange", (next) => {
|
|
6476
|
+
if (next === "destroyed" /* Destroyed */ && voiceConnections.get(created.guildId) === created)
|
|
6477
|
+
voiceConnections.delete(created.guildId);
|
|
6478
|
+
});
|
|
6479
|
+
voiceConnections.set(options.guildId, connection);
|
|
6480
|
+
}
|
|
6481
|
+
connection.setChannel(options.channelId);
|
|
6482
|
+
connection.setSuppression(options);
|
|
6483
|
+
connection.connect();
|
|
6484
|
+
return connection;
|
|
6485
|
+
}
|
|
6486
|
+
function getVoiceConnection(guildId) {
|
|
6487
|
+
return voiceConnections.get(guildId);
|
|
6488
|
+
}
|
|
6489
|
+
function createAudioPlayer() {
|
|
6490
|
+
return new AudioPlayer;
|
|
6491
|
+
}
|
|
6492
|
+
function createAudioResource(stream, metadata) {
|
|
6493
|
+
return new AudioStream(stream, metadata);
|
|
6494
|
+
}
|
|
6495
|
+
function entersState(target, status, timeoutMs) {
|
|
6496
|
+
if (target.state === status)
|
|
6497
|
+
return Promise.resolve(target);
|
|
6498
|
+
const emitter = target;
|
|
6499
|
+
const isConnection = target instanceof VoiceConnection;
|
|
6500
|
+
return new Promise((resolve, reject) => {
|
|
6501
|
+
const onChange = (first, second) => {
|
|
6502
|
+
if ((isConnection ? first : second) !== status)
|
|
6503
|
+
return;
|
|
6504
|
+
clearTimeout(timer);
|
|
6505
|
+
emitter.off("stateChange", onChange);
|
|
6506
|
+
resolve(target);
|
|
6507
|
+
};
|
|
6508
|
+
const timer = setTimeout(() => {
|
|
6509
|
+
emitter.off("stateChange", onChange);
|
|
6510
|
+
reject(new VoiceError(`Did not enter state "${String(status)}" within ${timeoutMs}ms.`));
|
|
6511
|
+
}, timeoutMs);
|
|
6512
|
+
emitter.on("stateChange", onChange);
|
|
6513
|
+
});
|
|
6514
|
+
}
|
|
5296
6515
|
// packages/builders/src/embed.ts
|
|
5297
6516
|
class EmbedBuilder {
|
|
5298
6517
|
#data = {};
|
|
@@ -5531,9 +6750,9 @@ class ButtonBuilder {
|
|
|
5531
6750
|
}
|
|
5532
6751
|
setURL(value) {
|
|
5533
6752
|
validateText(value, 512, "Button URL");
|
|
5534
|
-
let
|
|
6753
|
+
let url;
|
|
5535
6754
|
try {
|
|
5536
|
-
|
|
6755
|
+
url = new URL(value);
|
|
5537
6756
|
} catch (error) {
|
|
5538
6757
|
throw new TypeError("Button URL must be a valid URL.", {
|
|
5539
6758
|
cause: error
|
|
@@ -5541,7 +6760,7 @@ class ButtonBuilder {
|
|
|
5541
6760
|
}
|
|
5542
6761
|
this.#data.style = ButtonStyle.Link;
|
|
5543
6762
|
delete this.#data.custom_id;
|
|
5544
|
-
this.#data.url =
|
|
6763
|
+
this.#data.url = url.toString();
|
|
5545
6764
|
return this;
|
|
5546
6765
|
}
|
|
5547
6766
|
setEmoji(emoji) {
|
|
@@ -5698,8 +6917,8 @@ class ContainerBuilder {
|
|
|
5698
6917
|
this.#accentColor = color;
|
|
5699
6918
|
return this;
|
|
5700
6919
|
}
|
|
5701
|
-
setSpoiler(
|
|
5702
|
-
this.#spoiler =
|
|
6920
|
+
setSpoiler(spoiler = true) {
|
|
6921
|
+
this.#spoiler = spoiler;
|
|
5703
6922
|
return this;
|
|
5704
6923
|
}
|
|
5705
6924
|
toJSON() {
|
|
@@ -5775,13 +6994,13 @@ class MediaGalleryBuilder {
|
|
|
5775
6994
|
class FileComponentBuilder {
|
|
5776
6995
|
#url = "";
|
|
5777
6996
|
#spoiler;
|
|
5778
|
-
setUrl(
|
|
5779
|
-
validateText(
|
|
5780
|
-
this.#url =
|
|
6997
|
+
setUrl(url) {
|
|
6998
|
+
validateText(url, 2048, "File component URL");
|
|
6999
|
+
this.#url = url;
|
|
5781
7000
|
return this;
|
|
5782
7001
|
}
|
|
5783
|
-
setSpoiler(
|
|
5784
|
-
this.#spoiler =
|
|
7002
|
+
setSpoiler(spoiler = true) {
|
|
7003
|
+
this.#spoiler = spoiler;
|
|
5785
7004
|
return this;
|
|
5786
7005
|
}
|
|
5787
7006
|
toJSON() {
|
|
@@ -5824,9 +7043,9 @@ class ThumbnailBuilder {
|
|
|
5824
7043
|
#url = "";
|
|
5825
7044
|
#description;
|
|
5826
7045
|
#spoiler;
|
|
5827
|
-
setUrl(
|
|
5828
|
-
validateText(
|
|
5829
|
-
this.#url =
|
|
7046
|
+
setUrl(url) {
|
|
7047
|
+
validateText(url, 2048, "Thumbnail URL");
|
|
7048
|
+
this.#url = url;
|
|
5830
7049
|
return this;
|
|
5831
7050
|
}
|
|
5832
7051
|
setDescription(description) {
|
|
@@ -5834,8 +7053,8 @@ class ThumbnailBuilder {
|
|
|
5834
7053
|
this.#description = description;
|
|
5835
7054
|
return this;
|
|
5836
7055
|
}
|
|
5837
|
-
setSpoiler(
|
|
5838
|
-
this.#spoiler =
|
|
7056
|
+
setSpoiler(spoiler = true) {
|
|
7057
|
+
this.#spoiler = spoiler;
|
|
5839
7058
|
return this;
|
|
5840
7059
|
}
|
|
5841
7060
|
toJSON() {
|
|
@@ -5881,6 +7100,29 @@ function validateLength(value, field, max = 4000) {
|
|
|
5881
7100
|
if (!Number.isInteger(value) || value < 0 || value > max)
|
|
5882
7101
|
throw new RangeError(`${field} must be an integer between 0 and ${max}.`);
|
|
5883
7102
|
}
|
|
7103
|
+
class UserSelectMenuBuilder extends EntitySelectBuilder {
|
|
7104
|
+
constructor() {
|
|
7105
|
+
super(ComponentType.UserSelect);
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7109
|
+
class RoleSelectMenuBuilder extends EntitySelectBuilder {
|
|
7110
|
+
constructor() {
|
|
7111
|
+
super(ComponentType.RoleSelect);
|
|
7112
|
+
}
|
|
7113
|
+
}
|
|
7114
|
+
|
|
7115
|
+
class MentionableSelectMenuBuilder extends EntitySelectBuilder {
|
|
7116
|
+
constructor() {
|
|
7117
|
+
super(ComponentType.MentionableSelect);
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
|
|
7121
|
+
class ChannelSelectMenuBuilder extends EntitySelectBuilder {
|
|
7122
|
+
constructor() {
|
|
7123
|
+
super(ComponentType.ChannelSelect);
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
5884
7126
|
// packages/builders/src/commands.ts
|
|
5885
7127
|
var ApplicationCommandOptionType2 = {
|
|
5886
7128
|
Subcommand: 1,
|
|
@@ -6239,9 +7481,15 @@ function validateNumberRange(value, minimum, maximum, field, integerOnly = true)
|
|
|
6239
7481
|
|
|
6240
7482
|
class ContextMenuCommandBuilder {
|
|
6241
7483
|
data;
|
|
6242
|
-
constructor(type) {
|
|
7484
|
+
constructor(type = 2) {
|
|
6243
7485
|
this.data = { type };
|
|
6244
7486
|
}
|
|
7487
|
+
setType(type) {
|
|
7488
|
+
if (type !== 2 && type !== 3)
|
|
7489
|
+
throw new RangeError("Context menu command type must be 2 or 3.");
|
|
7490
|
+
this.data.type = type;
|
|
7491
|
+
return this;
|
|
7492
|
+
}
|
|
6245
7493
|
setName(name) {
|
|
6246
7494
|
if (typeof name !== "string" || name.trim().length < 1 || name.length > 32)
|
|
6247
7495
|
throw new RangeError("Command name must contain 1-32 characters.");
|
|
@@ -6343,9 +7591,12 @@ export {
|
|
|
6343
7591
|
ButtonBuilder,
|
|
6344
7592
|
ButtonStyle,
|
|
6345
7593
|
Cache,
|
|
7594
|
+
ResourceManager as CachedManager,
|
|
7595
|
+
CategoryChannel,
|
|
6346
7596
|
Channel,
|
|
6347
7597
|
ChannelManager,
|
|
6348
7598
|
ChannelOptionBuilder,
|
|
7599
|
+
ChannelSelectMenuBuilder,
|
|
6349
7600
|
ChannelType,
|
|
6350
7601
|
Client,
|
|
6351
7602
|
ClientEvent,
|
|
@@ -6358,113 +7609,165 @@ export {
|
|
|
6358
7609
|
ComponentType,
|
|
6359
7610
|
ContainerBuilder,
|
|
6360
7611
|
ContentInventoryEntryBuilder,
|
|
7612
|
+
ContextMenuCommandBuilder,
|
|
7613
|
+
DMChannel,
|
|
6361
7614
|
RESTError as DiscordAPIError,
|
|
6362
7615
|
Embed,
|
|
6363
7616
|
EmbedBuilder,
|
|
6364
7617
|
Emoji,
|
|
6365
7618
|
EmojiManager,
|
|
6366
7619
|
EntitySelectBuilder,
|
|
7620
|
+
ClientEvent as Events,
|
|
7621
|
+
FATAL_CLOSE_CODES,
|
|
6367
7622
|
FileComponentBuilder,
|
|
7623
|
+
ForumChannel,
|
|
6368
7624
|
Gateway,
|
|
6369
7625
|
GatewayCloseCodes,
|
|
6370
7626
|
GatewayError,
|
|
7627
|
+
GatewayHeartbeat,
|
|
6371
7628
|
GatewayIntentBits,
|
|
6372
7629
|
GatewayOpcodes,
|
|
7630
|
+
GatewayReconnect,
|
|
7631
|
+
GatewaySession,
|
|
6373
7632
|
GatewayState,
|
|
6374
7633
|
Guild,
|
|
7634
|
+
GuildBanManager,
|
|
6375
7635
|
GuildManager,
|
|
6376
7636
|
GuildMember,
|
|
6377
7637
|
GuildMemberManager,
|
|
6378
7638
|
GuildOnboarding,
|
|
7639
|
+
GuildScheduledEventManager,
|
|
6379
7640
|
GuildWelcomeScreen,
|
|
6380
7641
|
HandlerRegistry,
|
|
7642
|
+
HttpTransport,
|
|
7643
|
+
IDENTIFY_CLOSE_CODES,
|
|
6381
7644
|
IntegerOptionBuilder,
|
|
6382
7645
|
IntentBits,
|
|
6383
7646
|
Intents,
|
|
7647
|
+
IntentsBitField,
|
|
6384
7648
|
Interaction,
|
|
6385
7649
|
InteractionResponse,
|
|
6386
7650
|
InteractionResponseType,
|
|
6387
7651
|
InteractionType,
|
|
6388
7652
|
Invite,
|
|
6389
7653
|
Manager,
|
|
7654
|
+
MediaChannel,
|
|
6390
7655
|
MediaGalleryBuilder,
|
|
6391
7656
|
MemoryRateLimitStore,
|
|
6392
7657
|
MentionableOptionBuilder,
|
|
7658
|
+
MentionableSelectMenuBuilder,
|
|
6393
7659
|
Message,
|
|
6394
7660
|
MessageCommandBuilder,
|
|
6395
7661
|
MessageFlags,
|
|
6396
7662
|
MessageManager,
|
|
6397
7663
|
ModalBuilder,
|
|
6398
7664
|
ModalSubmitInteraction,
|
|
7665
|
+
NewsChannel,
|
|
6399
7666
|
NumberOptionBuilder,
|
|
6400
7667
|
OnboardingPrompt,
|
|
6401
7668
|
OnboardingPromptOption,
|
|
6402
7669
|
Permission,
|
|
6403
7670
|
PermissionFlagsBits,
|
|
7671
|
+
PermissionOverwriteManager,
|
|
6404
7672
|
PermissionOverwriteType,
|
|
6405
7673
|
PermissionSet,
|
|
6406
7674
|
Permissions,
|
|
6407
7675
|
PermissionsBitField,
|
|
7676
|
+
PlainTextDecoder,
|
|
6408
7677
|
PremiumTier,
|
|
6409
7678
|
REST,
|
|
6410
7679
|
RESTError,
|
|
7680
|
+
RateLimiter,
|
|
6411
7681
|
RedisRateLimitStore,
|
|
7682
|
+
RequestScheduler,
|
|
6412
7683
|
ResourceManager,
|
|
7684
|
+
ResponseDecoder,
|
|
6413
7685
|
Role,
|
|
6414
7686
|
RoleManager,
|
|
6415
7687
|
RoleOptionBuilder,
|
|
7688
|
+
RoleSelectMenuBuilder,
|
|
6416
7689
|
Routes,
|
|
6417
7690
|
SectionBuilder,
|
|
6418
7691
|
SeparatorBuilder,
|
|
6419
7692
|
ShardBus,
|
|
6420
7693
|
ShardManager,
|
|
7694
|
+
ShardManager as ShardingManager,
|
|
6421
7695
|
SlashCommandBuilder,
|
|
6422
7696
|
SpeakingFlags,
|
|
7697
|
+
StageChannel,
|
|
7698
|
+
StageInstanceManager,
|
|
6423
7699
|
GatewayState as Status,
|
|
6424
7700
|
StickerFormatType,
|
|
6425
7701
|
StickerType,
|
|
6426
7702
|
StringOptionBuilder,
|
|
6427
7703
|
StringSelectBuilder,
|
|
7704
|
+
StringSelectBuilder as StringSelectMenuBuilder,
|
|
6428
7705
|
SubcommandBuilder,
|
|
6429
7706
|
SubcommandGroupBuilder,
|
|
6430
7707
|
TextChannel,
|
|
6431
7708
|
TextDisplayBuilder,
|
|
6432
7709
|
TextInputBuilder,
|
|
6433
7710
|
TextInputStyle,
|
|
7711
|
+
ThreadChannel,
|
|
6434
7712
|
ThreadManager,
|
|
6435
7713
|
ThumbnailBuilder,
|
|
7714
|
+
TransportError,
|
|
6436
7715
|
User,
|
|
6437
7716
|
UserCommandBuilder,
|
|
6438
7717
|
UserManager,
|
|
6439
7718
|
UserOptionBuilder,
|
|
7719
|
+
UserSelectMenuBuilder,
|
|
6440
7720
|
VerificationLevel,
|
|
7721
|
+
VoiceChannel,
|
|
6441
7722
|
VoiceConnection,
|
|
6442
7723
|
VoiceConnectionState,
|
|
7724
|
+
VoiceConnectionState as VoiceConnectionStatus,
|
|
6443
7725
|
VoiceError,
|
|
6444
7726
|
VoiceReceiver,
|
|
7727
|
+
WebSocketTransport,
|
|
6445
7728
|
Webhook,
|
|
6446
7729
|
WebhookClient,
|
|
6447
7730
|
WebhookType,
|
|
6448
7731
|
WelcomeScreenChannel,
|
|
7732
|
+
ZlibStreamDecoder,
|
|
7733
|
+
abortError,
|
|
6449
7734
|
blockQuote,
|
|
6450
7735
|
bold,
|
|
6451
7736
|
bulletList,
|
|
6452
7737
|
channelMention,
|
|
7738
|
+
classifyCloseCode,
|
|
7739
|
+
classifyFrame,
|
|
7740
|
+
classifyPayload,
|
|
6453
7741
|
codeBlock,
|
|
7742
|
+
createAudioPlayer,
|
|
7743
|
+
createAudioResource,
|
|
7744
|
+
createChannel,
|
|
7745
|
+
createDecoder,
|
|
6454
7746
|
createInteraction,
|
|
6455
7747
|
createRetryPolicy,
|
|
7748
|
+
createRouteKey,
|
|
7749
|
+
entersState,
|
|
6456
7750
|
escapeMarkdown,
|
|
7751
|
+
getVoiceConnection,
|
|
6457
7752
|
heading,
|
|
7753
|
+
heartbeatPayload,
|
|
7754
|
+
identifyPayload,
|
|
6458
7755
|
inlineCode,
|
|
6459
7756
|
isSnowflake,
|
|
6460
7757
|
italic,
|
|
7758
|
+
joinVoiceChannel,
|
|
6461
7759
|
link,
|
|
7760
|
+
majorParameter,
|
|
6462
7761
|
masked,
|
|
7762
|
+
normalizeRoutePath,
|
|
6463
7763
|
orderedList,
|
|
6464
7764
|
randomInt,
|
|
6465
7765
|
resolveGatewayIntents,
|
|
7766
|
+
resumePayload,
|
|
6466
7767
|
roleMention,
|
|
6467
|
-
|
|
7768
|
+
scopeBucket,
|
|
7769
|
+
serializeQuery,
|
|
7770
|
+
sleep4 as sleep,
|
|
6468
7771
|
spoiler,
|
|
6469
7772
|
strikethrough,
|
|
6470
7773
|
subtext,
|
|
@@ -6473,4 +7776,4 @@ export {
|
|
|
6473
7776
|
userMention
|
|
6474
7777
|
};
|
|
6475
7778
|
|
|
6476
|
-
//# debugId=
|
|
7779
|
+
//# debugId=58F0E8EFC21C568E64756E2164756E21
|