lunibee 0.1.6 → 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 +39 -0
- package/dist/builders/components.d.ts +37 -12
- package/dist/builders/embed.d.ts +3 -1
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +192 -29
- package/dist/builders/index.js.map +9 -7
- package/dist/collection/index.d.ts +30 -0
- package/dist/collection/index.js +79 -5
- package/dist/collection/index.js.map +4 -4
- package/dist/core/events.d.ts +88 -2
- package/dist/core/index.d.ts +46 -85
- package/dist/core/index.js +2395 -789
- package/dist/core/index.js.map +44 -22
- package/dist/core/permissions.d.ts +112 -53
- package/dist/formatters/index.d.ts +43 -3
- package/dist/formatters/index.js +64 -1
- package/dist/formatters/index.js.map +3 -3
- package/dist/handlers/index.js.map +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3783 -1009
- package/dist/index.js.map +63 -38
- package/dist/managers/emoji.d.ts +38 -0
- package/dist/managers/guild-resources.d.ts +109 -0
- package/dist/managers/guild.d.ts +117 -1
- package/dist/managers/index.d.ts +37 -1
- package/dist/managers/index.js +1125 -81
- package/dist/managers/index.js.map +25 -20
- package/dist/rest/decoder.d.ts +23 -0
- package/dist/rest/errors.d.ts +20 -0
- package/dist/rest/index.d.ts +119 -25
- package/dist/rest/index.js +676 -204
- package/dist/rest/index.js.map +14 -6
- package/dist/rest/limiter.d.ts +42 -0
- package/dist/rest/redis.d.ts +54 -0
- package/dist/rest/route.d.ts +31 -0
- package/dist/rest/routes.d.ts +24 -0
- package/dist/rest/scheduler.d.ts +42 -0
- package/dist/rest/store.d.ts +67 -0
- package/dist/rest/transport.d.ts +40 -0
- package/dist/rest/webhook.d.ts +2 -2
- package/dist/sharding/bus.d.ts +31 -0
- package/dist/sharding/cluster.d.ts +71 -0
- package/dist/sharding/index.d.ts +18 -2
- package/dist/sharding/index.js +1202 -317
- package/dist/sharding/index.js.map +18 -7
- package/dist/structures/audit-log.d.ts +26 -0
- package/dist/structures/base.d.ts +40 -0
- package/dist/structures/channels.d.ts +56 -0
- package/dist/structures/index.d.ts +30 -1
- package/dist/structures/index.js +963 -87
- package/dist/structures/index.js.map +17 -10
- package/dist/structures/interactions.d.ts +12 -52
- package/dist/structures/options.d.ts +55 -0
- package/dist/structures/resources.d.ts +194 -6
- package/dist/types/gateway-events.d.ts +132 -0
- package/dist/types/gateway.d.ts +181 -0
- package/dist/types/index.d.ts +78 -263
- package/dist/types/index.js +44 -2
- package/dist/types/index.js.map +5 -4
- package/dist/utils/index.js.map +2 -2
- package/dist/voice/index.d.ts +116 -0
- package/dist/voice/index.js +261 -10
- 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 +24 -32
- package/dist/ws/index.js +920 -318
- 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 +23 -22
- package/dist/structures/permissions.d.ts +0 -73
package/dist/core/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/core/src/permissions.ts
|
|
3
3
|
var PermissionOverwriteType;
|
|
4
|
-
((
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
((PermissionOverwriteType) => {
|
|
5
|
+
PermissionOverwriteType[PermissionOverwriteType["Role"] = 0] = "Role";
|
|
6
|
+
PermissionOverwriteType[PermissionOverwriteType["Member"] = 1] = "Member";
|
|
7
7
|
})(PermissionOverwriteType ||= {});
|
|
8
8
|
var Permission = {
|
|
9
9
|
createInstantInvite: 1n << 0n,
|
|
@@ -107,58 +107,57 @@ var Permissions = {
|
|
|
107
107
|
SendPolls: 1n << 49n,
|
|
108
108
|
UseExternalApps: 1n << 50n
|
|
109
109
|
};
|
|
110
|
-
var PermissionFlagsBits
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})(PermissionFlagsBits ||= {});
|
|
110
|
+
var PermissionFlagsBits = {
|
|
111
|
+
CreateInstantInvite: 1n << 0n,
|
|
112
|
+
KickMembers: 1n << 1n,
|
|
113
|
+
BanMembers: 1n << 2n,
|
|
114
|
+
Administrator: 1n << 3n,
|
|
115
|
+
ManageChannels: 1n << 4n,
|
|
116
|
+
ManageGuild: 1n << 5n,
|
|
117
|
+
AddReactions: 1n << 6n,
|
|
118
|
+
ViewAuditLog: 1n << 7n,
|
|
119
|
+
PrioritySpeaker: 1n << 8n,
|
|
120
|
+
Stream: 1n << 9n,
|
|
121
|
+
ViewChannel: 1n << 10n,
|
|
122
|
+
SendMessages: 1n << 11n,
|
|
123
|
+
SendTTSMessages: 1n << 12n,
|
|
124
|
+
ManageMessages: 1n << 13n,
|
|
125
|
+
EmbedLinks: 1n << 14n,
|
|
126
|
+
AttachFiles: 1n << 15n,
|
|
127
|
+
ReadMessageHistory: 1n << 16n,
|
|
128
|
+
MentionEveryone: 1n << 17n,
|
|
129
|
+
UseExternalEmojis: 1n << 18n,
|
|
130
|
+
ViewGuildInsights: 1n << 19n,
|
|
131
|
+
Connect: 1n << 20n,
|
|
132
|
+
Speak: 1n << 21n,
|
|
133
|
+
MuteMembers: 1n << 22n,
|
|
134
|
+
DeafenMembers: 1n << 23n,
|
|
135
|
+
MoveMembers: 1n << 24n,
|
|
136
|
+
UseVAD: 1n << 25n,
|
|
137
|
+
ChangeNickname: 1n << 26n,
|
|
138
|
+
ManageNicknames: 1n << 27n,
|
|
139
|
+
ManageRoles: 1n << 28n,
|
|
140
|
+
ManageWebhooks: 1n << 29n,
|
|
141
|
+
ManageGuildExpressions: 1n << 30n,
|
|
142
|
+
UseApplicationCommands: 1n << 31n,
|
|
143
|
+
RequestToSpeak: 1n << 32n,
|
|
144
|
+
ManageEvents: 1n << 33n,
|
|
145
|
+
ManageThreads: 1n << 34n,
|
|
146
|
+
CreatePublicThreads: 1n << 35n,
|
|
147
|
+
CreatePrivateThreads: 1n << 36n,
|
|
148
|
+
UseExternalStickers: 1n << 37n,
|
|
149
|
+
SendMessagesInThreads: 1n << 38n,
|
|
150
|
+
UseEmbeddedActivities: 1n << 39n,
|
|
151
|
+
ModerateMembers: 1n << 40n,
|
|
152
|
+
ViewCreatorMonetizationAnalytics: 1n << 41n,
|
|
153
|
+
UseSoundboard: 1n << 42n,
|
|
154
|
+
CreateGuildExpressions: 1n << 43n,
|
|
155
|
+
CreateEvents: 1n << 44n,
|
|
156
|
+
UseExternalSounds: 1n << 45n,
|
|
157
|
+
SendVoiceMessages: 1n << 46n,
|
|
158
|
+
SendPolls: 1n << 49n,
|
|
159
|
+
UseExternalApps: 1n << 50n
|
|
160
|
+
};
|
|
162
161
|
|
|
163
162
|
class PermissionSet {
|
|
164
163
|
bitfield;
|
|
@@ -403,7 +402,7 @@ class PermissionSet {
|
|
|
403
402
|
if (permission in Permissions)
|
|
404
403
|
return Permissions[permission];
|
|
405
404
|
if (permission in PermissionFlagsBits)
|
|
406
|
-
return
|
|
405
|
+
return PermissionFlagsBits[permission];
|
|
407
406
|
const lower = permission.charAt(0).toLowerCase() + permission.slice(1);
|
|
408
407
|
if (lower in Permission)
|
|
409
408
|
return Permission[lower];
|
|
@@ -422,71 +421,147 @@ class PermissionsBitField extends PermissionSet {
|
|
|
422
421
|
}
|
|
423
422
|
// packages/core/src/events.ts
|
|
424
423
|
var ClientEvent;
|
|
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
|
-
|
|
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";
|
|
488
489
|
})(ClientEvent ||= {});
|
|
489
|
-
// packages/
|
|
490
|
+
// packages/core/src/collector.ts
|
|
491
|
+
import { EventEmitter } from "events";
|
|
492
|
+
|
|
493
|
+
class Collector extends EventEmitter {
|
|
494
|
+
collected = new Map;
|
|
495
|
+
options;
|
|
496
|
+
ended = false;
|
|
497
|
+
endReason;
|
|
498
|
+
totalProcessed = 0;
|
|
499
|
+
#timeoutTimer;
|
|
500
|
+
constructor(options = {}) {
|
|
501
|
+
super();
|
|
502
|
+
this.options = options;
|
|
503
|
+
if (options.time && options.time > 0) {
|
|
504
|
+
this.#timeoutTimer = setTimeout(() => {
|
|
505
|
+
this.stop("time");
|
|
506
|
+
}, options.time);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async handle(key, item) {
|
|
510
|
+
if (this.ended)
|
|
511
|
+
return false;
|
|
512
|
+
this.totalProcessed++;
|
|
513
|
+
const reachedProcessed = !!this.options.maxProcessed && this.totalProcessed >= this.options.maxProcessed;
|
|
514
|
+
let passed = true;
|
|
515
|
+
if (this.options.filter)
|
|
516
|
+
passed = !!await this.options.filter(item);
|
|
517
|
+
if (this.ended)
|
|
518
|
+
return false;
|
|
519
|
+
if (!passed) {
|
|
520
|
+
if (reachedProcessed)
|
|
521
|
+
this.stop("processedLimit");
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
this.collected.set(key, item);
|
|
525
|
+
this.emit("collect", item);
|
|
526
|
+
if (this.options.max && this.collected.size >= this.options.max) {
|
|
527
|
+
this.stop("limit");
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
if (reachedProcessed) {
|
|
531
|
+
this.stop("processedLimit");
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
stop(reason = "user") {
|
|
537
|
+
if (this.ended)
|
|
538
|
+
return;
|
|
539
|
+
this.ended = true;
|
|
540
|
+
this.endReason = reason;
|
|
541
|
+
if (this.#timeoutTimer)
|
|
542
|
+
clearTimeout(this.#timeoutTimer);
|
|
543
|
+
this.emit("end", this.collected, reason);
|
|
544
|
+
this.removeAllListeners();
|
|
545
|
+
}
|
|
546
|
+
next() {
|
|
547
|
+
return new Promise((resolve, reject) => {
|
|
548
|
+
if (this.ended) {
|
|
549
|
+
return reject(new Error(`Collector already ended: ${this.endReason ?? "unknown"}`));
|
|
550
|
+
}
|
|
551
|
+
const onCollect = (item) => {
|
|
552
|
+
this.off("end", onEnd);
|
|
553
|
+
resolve(item);
|
|
554
|
+
};
|
|
555
|
+
const onEnd = (_collected, reason) => {
|
|
556
|
+
this.off("collect", onCollect);
|
|
557
|
+
reject(new Error(`Collector ended before item was collected: ${reason}`));
|
|
558
|
+
};
|
|
559
|
+
this.once("collect", onCollect);
|
|
560
|
+
this.once("end", onEnd);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// packages/types/src/gateway.ts
|
|
490
565
|
var GatewayIntentBits = {
|
|
491
566
|
Guilds: 1 << 0,
|
|
492
567
|
GuildMembers: 1 << 1,
|
|
@@ -542,6 +617,8 @@ var Intents = IntentBits;
|
|
|
542
617
|
function resolveGatewayIntents(intents) {
|
|
543
618
|
if (typeof intents === "number")
|
|
544
619
|
return intents;
|
|
620
|
+
if (intents && typeof intents === "object" && !Array.isArray(intents))
|
|
621
|
+
return intents.bitfield;
|
|
545
622
|
if (Array.isArray(intents)) {
|
|
546
623
|
return intents.reduce((acc, intent) => {
|
|
547
624
|
return acc | resolveGatewayIntents(intent);
|
|
@@ -561,6 +638,44 @@ function resolveGatewayIntents(intents) {
|
|
|
561
638
|
}
|
|
562
639
|
return 0;
|
|
563
640
|
}
|
|
641
|
+
|
|
642
|
+
class IntentsBitField {
|
|
643
|
+
static Flags = GatewayIntentBits;
|
|
644
|
+
bitfield;
|
|
645
|
+
constructor(bits = 0) {
|
|
646
|
+
this.bitfield = resolveGatewayIntents(bits);
|
|
647
|
+
}
|
|
648
|
+
has(bits) {
|
|
649
|
+
const resolved = resolveGatewayIntents(bits);
|
|
650
|
+
return (this.bitfield & resolved) === resolved;
|
|
651
|
+
}
|
|
652
|
+
any(bits) {
|
|
653
|
+
return (this.bitfield & resolveGatewayIntents(bits)) !== 0;
|
|
654
|
+
}
|
|
655
|
+
add(...bits) {
|
|
656
|
+
for (const bit of bits)
|
|
657
|
+
this.bitfield |= resolveGatewayIntents(bit);
|
|
658
|
+
return this;
|
|
659
|
+
}
|
|
660
|
+
remove(...bits) {
|
|
661
|
+
for (const bit of bits)
|
|
662
|
+
this.bitfield &= ~resolveGatewayIntents(bit);
|
|
663
|
+
return this;
|
|
664
|
+
}
|
|
665
|
+
toArray() {
|
|
666
|
+
return Object.keys(GatewayIntentBits).filter((name) => this.has(GatewayIntentBits[name]));
|
|
667
|
+
}
|
|
668
|
+
equals(bits) {
|
|
669
|
+
return this.bitfield === resolveGatewayIntents(bits);
|
|
670
|
+
}
|
|
671
|
+
valueOf() {
|
|
672
|
+
return this.bitfield;
|
|
673
|
+
}
|
|
674
|
+
toJSON() {
|
|
675
|
+
return this.bitfield;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// packages/types/src/index.ts
|
|
564
679
|
var InteractionResponseType = {
|
|
565
680
|
Pong: 1,
|
|
566
681
|
ChannelMessage: 4,
|
|
@@ -716,6 +831,75 @@ class Collection extends Map {
|
|
|
716
831
|
}
|
|
717
832
|
return result;
|
|
718
833
|
}
|
|
834
|
+
partition(predicate) {
|
|
835
|
+
const pass = new Collection;
|
|
836
|
+
const fail = new Collection;
|
|
837
|
+
for (const [key, value] of this) {
|
|
838
|
+
if (predicate(value, key, this))
|
|
839
|
+
pass.set(key, value);
|
|
840
|
+
else
|
|
841
|
+
fail.set(key, value);
|
|
842
|
+
}
|
|
843
|
+
return [pass, fail];
|
|
844
|
+
}
|
|
845
|
+
tap(fn) {
|
|
846
|
+
fn(this);
|
|
847
|
+
return this;
|
|
848
|
+
}
|
|
849
|
+
flatMap(transform) {
|
|
850
|
+
const result = [];
|
|
851
|
+
for (const [key, value] of this) {
|
|
852
|
+
for (const item of transform(value, key, this))
|
|
853
|
+
result.push(item);
|
|
854
|
+
}
|
|
855
|
+
return result;
|
|
856
|
+
}
|
|
857
|
+
reduce(fn, initialValue) {
|
|
858
|
+
let acc = initialValue;
|
|
859
|
+
for (const [key, value] of this)
|
|
860
|
+
acc = fn(acc, value, key, this);
|
|
861
|
+
return acc;
|
|
862
|
+
}
|
|
863
|
+
sorted(comparator) {
|
|
864
|
+
const decorated = [...this.entries()].map((entry, index) => ({
|
|
865
|
+
entry,
|
|
866
|
+
index
|
|
867
|
+
}));
|
|
868
|
+
if (comparator)
|
|
869
|
+
decorated.sort((x, y) => {
|
|
870
|
+
const comparison = comparator(x.entry[1], y.entry[1], x.entry[0], y.entry[0]);
|
|
871
|
+
return comparison !== 0 ? comparison : x.index - y.index;
|
|
872
|
+
});
|
|
873
|
+
const result = new Collection;
|
|
874
|
+
for (const { entry } of decorated)
|
|
875
|
+
result.set(entry[0], entry[1]);
|
|
876
|
+
return result;
|
|
877
|
+
}
|
|
878
|
+
random() {
|
|
879
|
+
const arr = [...this.values()];
|
|
880
|
+
if (!arr.length)
|
|
881
|
+
return;
|
|
882
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
883
|
+
}
|
|
884
|
+
randomKey() {
|
|
885
|
+
const arr = [...this.keys()];
|
|
886
|
+
if (!arr.length)
|
|
887
|
+
return;
|
|
888
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
889
|
+
}
|
|
890
|
+
at(index) {
|
|
891
|
+
const arr = [...this.values()];
|
|
892
|
+
const i = index < 0 ? arr.length + index : index;
|
|
893
|
+
return arr[i];
|
|
894
|
+
}
|
|
895
|
+
keyAt(index) {
|
|
896
|
+
const arr = [...this.keys()];
|
|
897
|
+
const i = index < 0 ? arr.length + index : index;
|
|
898
|
+
return arr[i];
|
|
899
|
+
}
|
|
900
|
+
toJSON() {
|
|
901
|
+
return [...this.entries()];
|
|
902
|
+
}
|
|
719
903
|
}
|
|
720
904
|
|
|
721
905
|
// packages/managers/src/base.ts
|
|
@@ -787,7 +971,7 @@ class ResourceManager extends Manager {
|
|
|
787
971
|
// packages/rest/package.json
|
|
788
972
|
var package_default = {
|
|
789
973
|
name: "@lunibee/rest",
|
|
790
|
-
version: "0.1.
|
|
974
|
+
version: "0.1.8",
|
|
791
975
|
type: "module",
|
|
792
976
|
module: "./src/index.ts",
|
|
793
977
|
types: "./src/index.ts",
|
|
@@ -796,60 +980,57 @@ var package_default = {
|
|
|
796
980
|
}
|
|
797
981
|
};
|
|
798
982
|
|
|
799
|
-
// packages/rest/src/
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (!/^\d{1,20}$/.test(value))
|
|
848
|
-
throw new TypeError(`${field} must be a valid Discord snowflake.`);
|
|
849
|
-
return value;
|
|
983
|
+
// packages/rest/src/store.ts
|
|
984
|
+
class MemoryRateLimitStore {
|
|
985
|
+
#buckets = new Map;
|
|
986
|
+
#routeBuckets = new Map;
|
|
987
|
+
#globalResetAt = 0;
|
|
988
|
+
static #PRUNE_INTERVAL = 60000;
|
|
989
|
+
#lastPruneAt = 0;
|
|
990
|
+
getBucketHash(route) {
|
|
991
|
+
return this.#routeBuckets.get(route);
|
|
992
|
+
}
|
|
993
|
+
setBucketHash(route, hash) {
|
|
994
|
+
this.#routeBuckets.set(route, hash);
|
|
995
|
+
}
|
|
996
|
+
getBucket(key) {
|
|
997
|
+
return this.#buckets.get(key);
|
|
998
|
+
}
|
|
999
|
+
reserve(key) {
|
|
1000
|
+
const state = this.#buckets.get(key);
|
|
1001
|
+
const now = Date.now();
|
|
1002
|
+
if (!state || state.resetAt <= now)
|
|
1003
|
+
return { granted: true };
|
|
1004
|
+
if (state.remaining > 0) {
|
|
1005
|
+
state.remaining -= 1;
|
|
1006
|
+
return { granted: true };
|
|
1007
|
+
}
|
|
1008
|
+
return { granted: false, retryAfterMs: state.resetAt - now };
|
|
1009
|
+
}
|
|
1010
|
+
updateBucket(key, state) {
|
|
1011
|
+
this.#buckets.set(key, state);
|
|
1012
|
+
const now = Date.now();
|
|
1013
|
+
if (now - this.#lastPruneAt >= MemoryRateLimitStore.#PRUNE_INTERVAL) {
|
|
1014
|
+
this.#lastPruneAt = now;
|
|
1015
|
+
this.prune();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
prune(maxAge = 0) {
|
|
1019
|
+
const cutoff = Date.now() - Math.max(0, maxAge);
|
|
1020
|
+
for (const [key, state] of this.#buckets) {
|
|
1021
|
+
if (state.resetAt <= cutoff)
|
|
1022
|
+
this.#buckets.delete(key);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
getGlobalReset() {
|
|
1026
|
+
return this.#globalResetAt;
|
|
1027
|
+
}
|
|
1028
|
+
setGlobalReset(resetAt) {
|
|
1029
|
+
this.#globalResetAt = resetAt;
|
|
1030
|
+
}
|
|
850
1031
|
}
|
|
851
1032
|
|
|
852
|
-
// packages/rest/src/
|
|
1033
|
+
// packages/rest/src/errors.ts
|
|
853
1034
|
class RESTError extends Error {
|
|
854
1035
|
status;
|
|
855
1036
|
code;
|
|
@@ -866,196 +1047,254 @@ class RESTError extends Error {
|
|
|
866
1047
|
this.path = options.path;
|
|
867
1048
|
}
|
|
868
1049
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
function
|
|
873
|
-
if (
|
|
874
|
-
|
|
1050
|
+
function abortError(path, reason) {
|
|
1051
|
+
return new RESTError(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason });
|
|
1052
|
+
}
|
|
1053
|
+
async function sleep(delay, signal, path) {
|
|
1054
|
+
if (delay <= 0)
|
|
1055
|
+
return;
|
|
1056
|
+
await new Promise((resolve, reject) => {
|
|
1057
|
+
const timer = setTimeout(resolve, delay);
|
|
1058
|
+
const abort = () => {
|
|
1059
|
+
clearTimeout(timer);
|
|
1060
|
+
reject(abortError(path, signal?.reason));
|
|
1061
|
+
};
|
|
1062
|
+
if (signal?.aborted)
|
|
1063
|
+
return abort();
|
|
1064
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
async function abortable(promise, signal, path) {
|
|
1068
|
+
if (!signal)
|
|
1069
|
+
return promise;
|
|
1070
|
+
if (signal.aborted)
|
|
1071
|
+
throw abortError(path, signal.reason);
|
|
1072
|
+
return Promise.race([
|
|
1073
|
+
promise,
|
|
1074
|
+
new Promise((_, reject) => signal.addEventListener("abort", () => reject(abortError(path, signal.reason)), { once: true }))
|
|
1075
|
+
]);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// packages/rest/src/route.ts
|
|
1079
|
+
function normalizeRoutePath(path) {
|
|
1080
|
+
const queryIndex = path.indexOf("?");
|
|
1081
|
+
const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
|
|
1082
|
+
return routePath.replace(/\/\d+(?=\/|$)/g, "/:id");
|
|
1083
|
+
}
|
|
1084
|
+
function majorParameter(path) {
|
|
1085
|
+
const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/.exec(path);
|
|
1086
|
+
if (!match)
|
|
1087
|
+
return "@none";
|
|
1088
|
+
if (match[1] === "webhooks" && match[3] !== undefined)
|
|
1089
|
+
return `${match[2]}:${match[3]}`;
|
|
1090
|
+
return match[2];
|
|
1091
|
+
}
|
|
1092
|
+
function scopeBucket(hashOrRoute, major) {
|
|
1093
|
+
return `${hashOrRoute}|${major}`;
|
|
1094
|
+
}
|
|
1095
|
+
function createRouteKey(method, path) {
|
|
1096
|
+
const normalizedMethod = method.toUpperCase();
|
|
875
1097
|
return {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
},
|
|
881
|
-
getDelay(attempt, retryAfter) {
|
|
882
|
-
if (retryAfter !== undefined)
|
|
883
|
-
return Math.max(0, retryAfter * 1000);
|
|
884
|
-
return Math.min(1e4, 500 * 2 ** attempt) + Math.random() * 250;
|
|
885
|
-
}
|
|
1098
|
+
method: normalizedMethod,
|
|
1099
|
+
path,
|
|
1100
|
+
route: `${normalizedMethod}:${normalizeRoutePath(path)}`,
|
|
1101
|
+
major: majorParameter(path)
|
|
886
1102
|
};
|
|
887
1103
|
}
|
|
1104
|
+
function serializeQuery(query) {
|
|
1105
|
+
if (query === undefined)
|
|
1106
|
+
return "";
|
|
1107
|
+
if (typeof query === "string")
|
|
1108
|
+
return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
|
|
1109
|
+
const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
|
|
1110
|
+
const serialized = params.toString();
|
|
1111
|
+
return serialized.length === 0 ? "" : `?${serialized}`;
|
|
1112
|
+
}
|
|
888
1113
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
#
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1114
|
+
// packages/rest/src/scheduler.ts
|
|
1115
|
+
class RequestScheduler {
|
|
1116
|
+
static MAX_REMAPS = 2;
|
|
1117
|
+
#queues = new Map;
|
|
1118
|
+
get size() {
|
|
1119
|
+
return this.#queues.size;
|
|
1120
|
+
}
|
|
1121
|
+
async runResolved(resolveKey, signal, path, task) {
|
|
1122
|
+
let key = await resolveKey();
|
|
1123
|
+
for (let hop = 0;hop < RequestScheduler.MAX_REMAPS; hop++) {
|
|
1124
|
+
const outcome = await this.run(key, signal, path, async (release) => {
|
|
1125
|
+
const current = await resolveKey();
|
|
1126
|
+
if (current !== key)
|
|
1127
|
+
return { done: false, key: current };
|
|
1128
|
+
return { done: true, value: await task(key, release) };
|
|
1129
|
+
});
|
|
1130
|
+
if (outcome.done)
|
|
1131
|
+
return outcome.value;
|
|
1132
|
+
key = outcome.key;
|
|
1133
|
+
}
|
|
1134
|
+
return this.run(key, signal, path, (release) => task(key, release));
|
|
907
1135
|
}
|
|
908
|
-
async
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
throw new TypeError("REST paths must start with '/'.");
|
|
914
|
-
const route = `${normalizedMethod}:${this.#normalizeRoute(path)}`;
|
|
915
|
-
let bucketKey = this.#routeBuckets.get(route) ?? route;
|
|
916
|
-
let bucket = this.#buckets.get(bucketKey) ?? {
|
|
917
|
-
remaining: 1,
|
|
918
|
-
resetAt: 0,
|
|
919
|
-
queue: Promise.resolve()
|
|
920
|
-
};
|
|
921
|
-
this.#buckets.set(bucketKey, bucket);
|
|
922
|
-
const previous = bucket.queue;
|
|
923
|
-
let release;
|
|
924
|
-
bucket.queue = new Promise((resolve) => {
|
|
925
|
-
release = resolve;
|
|
1136
|
+
async run(key, signal, path, task) {
|
|
1137
|
+
const previous = this.#queues.get(key) ?? Promise.resolve();
|
|
1138
|
+
let open;
|
|
1139
|
+
const gate = new Promise((resolve) => {
|
|
1140
|
+
open = resolve;
|
|
926
1141
|
});
|
|
927
|
-
|
|
1142
|
+
this.#queues.set(key, gate);
|
|
1143
|
+
let released = false;
|
|
1144
|
+
const release = () => {
|
|
1145
|
+
if (released)
|
|
1146
|
+
return;
|
|
1147
|
+
released = true;
|
|
1148
|
+
open();
|
|
1149
|
+
if (this.#queues.get(key) === gate)
|
|
1150
|
+
this.#queues.delete(key);
|
|
1151
|
+
};
|
|
928
1152
|
try {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
await this.#waitGlobal(options.signal, path);
|
|
932
|
-
const controller = new AbortController;
|
|
933
|
-
const onAbort = () => controller.abort(options.signal?.reason);
|
|
934
|
-
if (options.signal?.aborted)
|
|
935
|
-
throw this.#abortError(path, options.signal.reason);
|
|
936
|
-
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
937
|
-
const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
|
|
938
|
-
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
939
|
-
const headers = {
|
|
940
|
-
...this.#token ? { Authorization: `Bot ${this.#token}` } : {},
|
|
941
|
-
"User-Agent": USER_AGENT
|
|
942
|
-
};
|
|
943
|
-
if (!isFormData)
|
|
944
|
-
headers["Content-Type"] = "application/json";
|
|
945
|
-
const fetchBody = isFormData ? body : body === undefined ? undefined : JSON.stringify(body);
|
|
946
|
-
try {
|
|
947
|
-
const response = await fetch(`${this.#baseURL}${path}`, {
|
|
948
|
-
method: normalizedMethod,
|
|
949
|
-
headers,
|
|
950
|
-
body: fetchBody,
|
|
951
|
-
signal: controller.signal
|
|
952
|
-
});
|
|
953
|
-
bucket = this.#update(response, bucket, route, bucketKey);
|
|
954
|
-
bucketKey = this.#routeBuckets.get(route) ?? bucketKey;
|
|
955
|
-
const payload = await this.#readPayload(response);
|
|
956
|
-
if (response.ok)
|
|
957
|
-
return response.status === 204 ? undefined : payload;
|
|
958
|
-
const data = this.#errorData(payload);
|
|
959
|
-
const retryAfter = response.status === 429 ? this.#retryAfter(response, data) : undefined;
|
|
960
|
-
if (data.global && retryAfter !== undefined)
|
|
961
|
-
this.#globalResetAt = Date.now() + retryAfter * 1000;
|
|
962
|
-
if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, response.status)) {
|
|
963
|
-
await this.#sleep(this.#retryPolicy.getDelay(attempt, retryAfter), options.signal, path);
|
|
964
|
-
continue;
|
|
965
|
-
}
|
|
966
|
-
throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method: normalizedMethod, path });
|
|
967
|
-
} catch (error) {
|
|
968
|
-
if (error instanceof RESTError)
|
|
969
|
-
throw error;
|
|
970
|
-
if (options.signal?.aborted)
|
|
971
|
-
throw this.#abortError(path, options.signal.reason);
|
|
972
|
-
const isTimeout = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
973
|
-
if (!isTimeout && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(normalizedMethod, 0)) {
|
|
974
|
-
await this.#sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
|
|
975
|
-
continue;
|
|
976
|
-
}
|
|
977
|
-
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 });
|
|
978
|
-
} finally {
|
|
979
|
-
clearTimeout(timer);
|
|
980
|
-
options.signal?.removeEventListener("abort", onAbort);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method: normalizedMethod, path });
|
|
1153
|
+
await abortable(previous, signal, path);
|
|
1154
|
+
return await task(release);
|
|
984
1155
|
} finally {
|
|
985
1156
|
release();
|
|
986
1157
|
}
|
|
987
1158
|
}
|
|
988
|
-
|
|
989
|
-
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// packages/rest/src/limiter.ts
|
|
1162
|
+
class RateLimiter {
|
|
1163
|
+
#store;
|
|
1164
|
+
#concurrent;
|
|
1165
|
+
constructor(store, options = {}) {
|
|
1166
|
+
this.#store = store;
|
|
1167
|
+
this.#concurrent = options.concurrent ?? false;
|
|
1168
|
+
}
|
|
1169
|
+
get store() {
|
|
1170
|
+
return this.#store;
|
|
1171
|
+
}
|
|
1172
|
+
async resolveBucketKey(route, major) {
|
|
1173
|
+
return scopeBucket(await this.#store.getBucketHash(route) ?? route, major);
|
|
1174
|
+
}
|
|
1175
|
+
get reserves() {
|
|
1176
|
+
return typeof this.#store.reserve === "function";
|
|
1177
|
+
}
|
|
1178
|
+
async acquire(bucketKey, signal, path) {
|
|
1179
|
+
await this.#waitGlobal(signal, path);
|
|
1180
|
+
if (!this.#store.reserve) {
|
|
1181
|
+
const bucket = await this.#store.getBucket(bucketKey);
|
|
1182
|
+
if (bucket) {
|
|
1183
|
+
const delay = bucket.resetAt - Date.now();
|
|
1184
|
+
if (bucket.remaining <= 0 && delay > 0)
|
|
1185
|
+
await sleep(delay, signal, path);
|
|
1186
|
+
}
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
for (let attempt = 0;attempt < 8; attempt++) {
|
|
1190
|
+
const reservation = await this.#store.reserve(bucketKey);
|
|
1191
|
+
if (reservation.granted)
|
|
1192
|
+
return;
|
|
1193
|
+
await sleep(Math.max(1, reservation.retryAfterMs), signal, path);
|
|
1194
|
+
await this.#waitGlobal(signal, path);
|
|
1195
|
+
}
|
|
990
1196
|
}
|
|
991
|
-
|
|
992
|
-
|
|
1197
|
+
async#waitGlobal(signal, path) {
|
|
1198
|
+
const globalDelay = await this.#store.getGlobalReset() - Date.now();
|
|
1199
|
+
if (globalDelay > 0)
|
|
1200
|
+
await sleep(globalDelay, signal, path);
|
|
993
1201
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1202
|
+
async noteGlobalReset(resetAt) {
|
|
1203
|
+
const current = await this.#store.getGlobalReset();
|
|
1204
|
+
await this.#store.setGlobalReset(Math.max(current, resetAt));
|
|
996
1205
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
#fileForm(payload, files) {
|
|
1004
|
-
const form = new FormData;
|
|
1005
|
-
form.append("payload_json", JSON.stringify(payload));
|
|
1006
|
-
for (let i = 0;i < files.length; i++) {
|
|
1007
|
-
const file = files[i];
|
|
1008
|
-
const blob = file.data instanceof Blob ? file.data : new Blob([file.data], {
|
|
1009
|
-
type: file.contentType ?? "application/octet-stream"
|
|
1010
|
-
});
|
|
1011
|
-
form.append(`files[${i}]`, blob, file.name);
|
|
1206
|
+
async applyResponse(response, bucketKey, route, major) {
|
|
1207
|
+
let currentKey = bucketKey;
|
|
1208
|
+
const serverBucket = response.headers.get("X-RateLimit-Bucket");
|
|
1209
|
+
if (serverBucket) {
|
|
1210
|
+
await this.#store.setBucketHash(route, serverBucket);
|
|
1211
|
+
currentKey = scopeBucket(serverBucket, major);
|
|
1012
1212
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1213
|
+
const stored = await this.#store.getBucket(currentKey);
|
|
1214
|
+
const bucket = stored ? { ...stored } : { remaining: 1, resetAt: 0 };
|
|
1215
|
+
const windowActive = this.#concurrent && !!stored && stored.resetAt > Date.now();
|
|
1216
|
+
const remaining = Number(response.headers.get("X-RateLimit-Remaining"));
|
|
1217
|
+
const resetAfter = Number(response.headers.get("X-RateLimit-Reset-After"));
|
|
1218
|
+
const reset = Number(response.headers.get("X-RateLimit-Reset"));
|
|
1219
|
+
if (Number.isFinite(remaining))
|
|
1220
|
+
bucket.remaining = Math.max(0, remaining);
|
|
1221
|
+
if (Number.isFinite(resetAfter))
|
|
1222
|
+
bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;
|
|
1223
|
+
else if (Number.isFinite(reset))
|
|
1224
|
+
bucket.resetAt = reset * 1000;
|
|
1225
|
+
if (windowActive && stored) {
|
|
1226
|
+
bucket.remaining = Math.min(bucket.remaining, stored.remaining);
|
|
1227
|
+
bucket.resetAt = Math.max(bucket.resetAt, stored.resetAt);
|
|
1228
|
+
}
|
|
1229
|
+
if (response.status === 429) {
|
|
1230
|
+
const retryAfter = Number(response.headers.get("Retry-After"));
|
|
1231
|
+
if (Number.isFinite(retryAfter))
|
|
1232
|
+
bucket.resetAt = Math.max(bucket.resetAt, Date.now() + retryAfter * 1000);
|
|
1233
|
+
}
|
|
1234
|
+
await this.#store.updateBucket(currentKey, bucket);
|
|
1235
|
+
if (response.headers.get("X-RateLimit-Global") === "true") {
|
|
1236
|
+
const retryAfter = Number(response.headers.get("Retry-After"));
|
|
1237
|
+
if (Number.isFinite(retryAfter))
|
|
1238
|
+
await this.noteGlobalReset(Date.now() + retryAfter * 1000);
|
|
1239
|
+
}
|
|
1240
|
+
return currentKey;
|
|
1020
1241
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// packages/rest/src/transport.ts
|
|
1245
|
+
class TransportError extends Error {
|
|
1246
|
+
timedOut;
|
|
1247
|
+
constructor(message, timedOut, cause) {
|
|
1248
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
1249
|
+
this.name = "TransportError";
|
|
1250
|
+
this.timedOut = timedOut;
|
|
1025
1251
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
class HttpTransport {
|
|
1255
|
+
#baseURL;
|
|
1256
|
+
#timeout;
|
|
1257
|
+
#fetch;
|
|
1258
|
+
constructor(options = {}) {
|
|
1259
|
+
this.#baseURL = (options.baseURL ?? "https://discord.com/api/v10").replace(/\/$/, "");
|
|
1260
|
+
this.#timeout = Math.max(1, options.timeout ?? 15000);
|
|
1261
|
+
this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
1030
1262
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
return;
|
|
1034
|
-
await new Promise((resolve, reject) => {
|
|
1035
|
-
const timer = setTimeout(resolve, delay);
|
|
1036
|
-
const abort = () => {
|
|
1037
|
-
clearTimeout(timer);
|
|
1038
|
-
reject(this.#abortError(path, signal?.reason));
|
|
1039
|
-
};
|
|
1040
|
-
if (signal?.aborted)
|
|
1041
|
-
return abort();
|
|
1042
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
1043
|
-
});
|
|
1263
|
+
get baseURL() {
|
|
1264
|
+
return this.#baseURL;
|
|
1044
1265
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
return promise;
|
|
1048
|
-
if (signal.aborted)
|
|
1049
|
-
throw this.#abortError(path, signal.reason);
|
|
1050
|
-
return Promise.race([
|
|
1051
|
-
promise,
|
|
1052
|
-
new Promise((_, reject) => signal.addEventListener("abort", () => reject(this.#abortError(path, signal.reason)), { once: true }))
|
|
1053
|
-
]);
|
|
1266
|
+
get timeout() {
|
|
1267
|
+
return this.#timeout;
|
|
1054
1268
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1269
|
+
async send(request) {
|
|
1270
|
+
if (request.signal?.aborted)
|
|
1271
|
+
throw abortError(request.path, request.signal.reason);
|
|
1272
|
+
const controller = new AbortController;
|
|
1273
|
+
const onAbort = () => controller.abort(request.signal?.reason);
|
|
1274
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1275
|
+
const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
|
|
1276
|
+
try {
|
|
1277
|
+
return await this.#fetch(`${this.#baseURL}${request.path}`, {
|
|
1278
|
+
method: request.method,
|
|
1279
|
+
headers: request.headers,
|
|
1280
|
+
body: request.body,
|
|
1281
|
+
signal: controller.signal
|
|
1282
|
+
});
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
if (error instanceof RESTError)
|
|
1285
|
+
throw error;
|
|
1286
|
+
const timedOut = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
1287
|
+
throw new TransportError(timedOut ? `Discord REST request timed out after ${this.#timeout}ms` : "Discord REST request failed", timedOut, error);
|
|
1288
|
+
} finally {
|
|
1289
|
+
clearTimeout(timer);
|
|
1290
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
1291
|
+
}
|
|
1057
1292
|
}
|
|
1058
|
-
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// packages/rest/src/decoder.ts
|
|
1296
|
+
class ResponseDecoder {
|
|
1297
|
+
async read(response) {
|
|
1059
1298
|
if (response.status === 204)
|
|
1060
1299
|
return;
|
|
1061
1300
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -1067,7 +1306,7 @@ class REST {
|
|
|
1067
1306
|
return;
|
|
1068
1307
|
});
|
|
1069
1308
|
}
|
|
1070
|
-
|
|
1309
|
+
errorData(payload) {
|
|
1071
1310
|
if (!payload || typeof payload !== "object")
|
|
1072
1311
|
return {};
|
|
1073
1312
|
const data = payload;
|
|
@@ -1078,46 +1317,310 @@ class REST {
|
|
|
1078
1317
|
global: data.global === true
|
|
1079
1318
|
};
|
|
1080
1319
|
}
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1320
|
+
retryAfter(response, data) {
|
|
1321
|
+
const raw = response.headers.get("Retry-After");
|
|
1322
|
+
const header = raw === null ? Number.NaN : Number(raw);
|
|
1083
1323
|
const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
|
|
1084
1324
|
return Math.max(0, retryAfter);
|
|
1085
1325
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// packages/rest/src/routes.ts
|
|
1329
|
+
var Routes = {
|
|
1330
|
+
user: () => "/users/@me",
|
|
1331
|
+
userById: (userId) => `/users/${snowflake(userId, "User ID")}`,
|
|
1332
|
+
guild: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}`,
|
|
1333
|
+
guildPreview: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/preview`,
|
|
1334
|
+
guildActiveThreads: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/threads/active`,
|
|
1335
|
+
guildWebhooks: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/webhooks`,
|
|
1336
|
+
guildInvites: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/invites`,
|
|
1337
|
+
guildChannels: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/channels`,
|
|
1338
|
+
guildMember: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/${snowflake(userId, "User ID")}`,
|
|
1339
|
+
guildRoles: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/roles`,
|
|
1340
|
+
guildRole: (guildId, roleId) => `/guilds/${snowflake(guildId, "Guild ID")}/roles/${snowflake(roleId, "Role ID")}`,
|
|
1341
|
+
guildMemberRole: (guildId, userId, roleId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/${snowflake(userId, "User ID")}/roles/${snowflake(roleId, "Role ID")}`,
|
|
1342
|
+
guildBans: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/bans`,
|
|
1343
|
+
guildBan: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/bans/${snowflake(userId, "User ID")}`,
|
|
1344
|
+
channel: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}`,
|
|
1345
|
+
channelMessages: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages`,
|
|
1346
|
+
message: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/${snowflake(messageId, "Message ID")}`,
|
|
1347
|
+
crosspostMessage: (channelId, messageId) => `${Routes.message(channelId, messageId)}/crosspost`,
|
|
1348
|
+
messageReactions: (channelId, messageId, emoji) => `${Routes.message(channelId, messageId)}/reactions/${encodeURIComponent(emoji)}`,
|
|
1349
|
+
messageReactionsAll: (channelId, messageId) => `${Routes.message(channelId, messageId)}/reactions`,
|
|
1350
|
+
channelPins: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/pins`,
|
|
1351
|
+
channelPin: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/pins/${snowflake(messageId, "Message ID")}`,
|
|
1352
|
+
messageThread: (channelId, messageId) => `${Routes.message(channelId, messageId)}/threads`,
|
|
1353
|
+
channelBulkDelete: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/bulk-delete`,
|
|
1354
|
+
channelWebhooks: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/webhooks`,
|
|
1355
|
+
channelInvites: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/invites`,
|
|
1356
|
+
webhook: (webhookId, token) => `/webhooks/${snowflake(webhookId, "Webhook ID")}${token ? `/${encodeURIComponent(token)}` : ""}`,
|
|
1357
|
+
webhookMessage: (webhookId, token, messageId) => `/webhooks/${snowflake(webhookId, "Webhook ID")}/${encodeURIComponent(token)}/messages/${snowflake(messageId, "Message ID")}`,
|
|
1358
|
+
applicationCommands: (applicationId) => `/applications/${snowflake(applicationId, "Application ID")}/commands`,
|
|
1359
|
+
applicationCommand: (applicationId, commandId) => `/applications/${snowflake(applicationId, "Application ID")}/commands/${snowflake(commandId, "Command ID")}`,
|
|
1360
|
+
guildApplicationCommands: (applicationId, guildId) => `/applications/${snowflake(applicationId, "Application ID")}/guilds/${snowflake(guildId, "Guild ID")}/commands`,
|
|
1361
|
+
guildApplicationCommand: (applicationId, guildId, commandId) => `/applications/${snowflake(applicationId, "Application ID")}/guilds/${snowflake(guildId, "Guild ID")}/commands/${snowflake(commandId, "Command ID")}`,
|
|
1362
|
+
interactionCallback: (interactionId, token) => `/interactions/${snowflake(interactionId, "Interaction ID")}/${encodeURIComponent(token)}/callback`,
|
|
1363
|
+
interactionOriginalResponse: (applicationId, token) => `/webhooks/${snowflake(applicationId, "Application ID")}/${encodeURIComponent(token)}/messages/@original`,
|
|
1364
|
+
channelPublicArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads/archived/public`,
|
|
1365
|
+
channelPrivateArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads/archived/private`,
|
|
1366
|
+
threadMembers: (threadId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members`,
|
|
1367
|
+
guildScheduledEvents: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events`,
|
|
1368
|
+
guildScheduledEvent: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}`,
|
|
1369
|
+
guildScheduledEventUsers: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}/users`,
|
|
1370
|
+
channelPermission: (channelId, overwriteId) => `/channels/${snowflake(channelId, "Channel ID")}/permissions/${snowflake(overwriteId, "Overwrite ID")}`,
|
|
1371
|
+
guildAutoModerationRules: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules`,
|
|
1372
|
+
guildAutoModerationRule: (guildId, ruleId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules/${snowflake(ruleId, "Rule ID")}`,
|
|
1373
|
+
voiceRegions: () => "/voice/regions",
|
|
1374
|
+
gateway: () => "/gateway",
|
|
1375
|
+
gatewayBot: () => "/gateway/bot",
|
|
1376
|
+
guildAuditLog: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/audit-logs`,
|
|
1377
|
+
guildMembers: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/members`,
|
|
1378
|
+
guildWelcomeScreen: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/welcome-screen`,
|
|
1379
|
+
guildOnboarding: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/onboarding`,
|
|
1380
|
+
guildPrune: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/prune`,
|
|
1381
|
+
stageInstances: () => "/stage-instances",
|
|
1382
|
+
stageInstanceByChannel: (channelId) => `/stage-instances/${snowflake(channelId, "Channel ID")}`,
|
|
1383
|
+
invite: (code) => `/invites/${encodeURIComponent(code)}`,
|
|
1384
|
+
guildVanity: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/vanity-url`,
|
|
1385
|
+
guildEmoji: (guildId, emojiId) => emojiId ? `/guilds/${snowflake(guildId, "Guild ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/emojis`,
|
|
1386
|
+
applicationEmoji: (applicationId, emojiId) => emojiId ? `/applications/${snowflake(applicationId, "Application ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/emojis`
|
|
1387
|
+
};
|
|
1388
|
+
function snowflake(value, field) {
|
|
1389
|
+
if (!/^\d{1,20}$/.test(value))
|
|
1390
|
+
throw new TypeError(`${field} must be a valid Discord snowflake.`);
|
|
1391
|
+
return value;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// packages/rest/src/index.ts
|
|
1395
|
+
var LIBRARY_VERSION = package_default.version;
|
|
1396
|
+
var LIBRARY_URL = "https://github.com/Ekretos/lunibee";
|
|
1397
|
+
var USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${package_default.version})`;
|
|
1398
|
+
var REQUEST_DATA_KEYS = new Set([
|
|
1399
|
+
"body",
|
|
1400
|
+
"files",
|
|
1401
|
+
"query",
|
|
1402
|
+
"headers",
|
|
1403
|
+
"reason",
|
|
1404
|
+
"auth",
|
|
1405
|
+
"signal"
|
|
1406
|
+
]);
|
|
1407
|
+
function isRequestData(value) {
|
|
1408
|
+
if (value === null || typeof value !== "object")
|
|
1409
|
+
return false;
|
|
1410
|
+
if (Array.isArray(value))
|
|
1411
|
+
return false;
|
|
1412
|
+
if (typeof FormData !== "undefined" && value instanceof FormData)
|
|
1413
|
+
return false;
|
|
1414
|
+
if (value instanceof Blob || value instanceof Uint8Array || value instanceof ArrayBuffer)
|
|
1415
|
+
return false;
|
|
1416
|
+
const keys = Object.keys(value);
|
|
1417
|
+
if (keys.length === 0)
|
|
1418
|
+
return false;
|
|
1419
|
+
return keys.every((key) => REQUEST_DATA_KEYS.has(key));
|
|
1420
|
+
}
|
|
1421
|
+
function createRetryPolicy(maxRetries = 2) {
|
|
1422
|
+
if (!Number.isFinite(maxRetries) || maxRetries < 0)
|
|
1423
|
+
throw new TypeError("maxRetries must be a non-negative finite number.");
|
|
1424
|
+
return {
|
|
1425
|
+
maxRetries: Math.floor(maxRetries),
|
|
1426
|
+
shouldRetry(method, status) {
|
|
1427
|
+
const normalized = method.toUpperCase();
|
|
1428
|
+
const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
|
|
1429
|
+
if (status === 0)
|
|
1430
|
+
return idempotent;
|
|
1431
|
+
return status === 429 || status >= 500 && status <= 599 && idempotent;
|
|
1432
|
+
},
|
|
1433
|
+
getDelay(attempt, retryAfter) {
|
|
1434
|
+
if (retryAfter !== undefined)
|
|
1435
|
+
return Math.max(0, retryAfter * 1000);
|
|
1436
|
+
return Math.min(1e4, 500 * 2 ** attempt) + Math.random() * 250;
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
class REST {
|
|
1442
|
+
#retryPolicy;
|
|
1443
|
+
#token;
|
|
1444
|
+
#limiter;
|
|
1445
|
+
#scheduler = new RequestScheduler;
|
|
1446
|
+
#transport;
|
|
1447
|
+
#decoder = new ResponseDecoder;
|
|
1448
|
+
#hooks = {};
|
|
1449
|
+
#concurrentBuckets;
|
|
1450
|
+
constructor(options = {}) {
|
|
1451
|
+
this.#token = options.token;
|
|
1452
|
+
this.#retryPolicy = options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);
|
|
1453
|
+
this.#transport = options.transport ?? new HttpTransport({
|
|
1454
|
+
baseURL: options.baseURL,
|
|
1455
|
+
timeout: options.timeout
|
|
1456
|
+
});
|
|
1457
|
+
const store = options.store ?? new MemoryRateLimitStore;
|
|
1458
|
+
this.#concurrentBuckets = (options.concurrentBuckets ?? false) && typeof store.reserve === "function";
|
|
1459
|
+
this.#limiter = new RateLimiter(store, {
|
|
1460
|
+
concurrent: this.#concurrentBuckets
|
|
1461
|
+
});
|
|
1462
|
+
if (options.hooks)
|
|
1463
|
+
this.#hooks = options.hooks;
|
|
1090
1464
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1465
|
+
get store() {
|
|
1466
|
+
return this.#limiter.store;
|
|
1467
|
+
}
|
|
1468
|
+
setHooks(hooks) {
|
|
1469
|
+
this.#hooks = hooks;
|
|
1470
|
+
return this;
|
|
1471
|
+
}
|
|
1472
|
+
setToken(token) {
|
|
1473
|
+
if (!token.trim())
|
|
1474
|
+
throw new TypeError("A Discord bot token is required.");
|
|
1475
|
+
this.#token = token;
|
|
1476
|
+
}
|
|
1477
|
+
async request(method, path, body, options = {}) {
|
|
1478
|
+
if (!method.toUpperCase())
|
|
1479
|
+
throw new TypeError("REST method is required.");
|
|
1480
|
+
if (!path.startsWith("/"))
|
|
1481
|
+
throw new TypeError("REST paths must start with '/'.");
|
|
1482
|
+
const route = createRouteKey(method, path);
|
|
1483
|
+
const files = options.files;
|
|
1484
|
+
if (files && files.length > 0 && !(body instanceof FormData))
|
|
1485
|
+
body = this.#fileForm(body, files);
|
|
1486
|
+
const query = serializeQuery(options.query);
|
|
1487
|
+
const requestPath = query ? `${path}${query}` : path;
|
|
1488
|
+
return this.#scheduler.runResolved(() => this.#limiter.resolveBucketKey(route.route, route.major), options.signal, path, (bucketKey, release) => this.#attempts(route, requestPath, body, options, bucketKey, release));
|
|
1489
|
+
}
|
|
1490
|
+
async#attempts(route, requestPath, body, options, initialBucketKey, release) {
|
|
1491
|
+
const { method, path } = route;
|
|
1492
|
+
let bucketKey = initialBucketKey;
|
|
1493
|
+
for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
|
|
1494
|
+
await this.#limiter.acquire(bucketKey, options.signal, path);
|
|
1495
|
+
if (this.#concurrentBuckets) {
|
|
1496
|
+
const bucket = await this.#limiter.store.getBucket(bucketKey);
|
|
1497
|
+
if (bucket && bucket.resetAt > Date.now())
|
|
1498
|
+
release();
|
|
1099
1499
|
}
|
|
1500
|
+
this.#emit(this.#hooks.onRequest, { method, path, attempt });
|
|
1501
|
+
const requestStart = Date.now();
|
|
1502
|
+
let response;
|
|
1503
|
+
try {
|
|
1504
|
+
response = await this.#transport.send({
|
|
1505
|
+
method,
|
|
1506
|
+
path: requestPath,
|
|
1507
|
+
headers: this.#headers(body, options),
|
|
1508
|
+
body: this.#encodeBody(body),
|
|
1509
|
+
signal: options.signal
|
|
1510
|
+
});
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
if (error instanceof RESTError)
|
|
1513
|
+
throw error;
|
|
1514
|
+
if (options.signal?.aborted)
|
|
1515
|
+
throw abortError(path, options.signal.reason);
|
|
1516
|
+
const failure = error instanceof TransportError ? error : new TransportError("Discord REST request failed", false, error);
|
|
1517
|
+
if (!failure.timedOut && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, 0)) {
|
|
1518
|
+
await sleep(this.#retryPolicy.getDelay(attempt), options.signal, path);
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
throw new RESTError(failure.message, 0, undefined, undefined, {
|
|
1522
|
+
method,
|
|
1523
|
+
path,
|
|
1524
|
+
cause: failure.cause ?? failure
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
this.#emit(this.#hooks.onResponse, {
|
|
1528
|
+
method,
|
|
1529
|
+
path,
|
|
1530
|
+
status: response.status,
|
|
1531
|
+
durationMs: Date.now() - requestStart
|
|
1532
|
+
});
|
|
1533
|
+
bucketKey = await this.#limiter.applyResponse(response, bucketKey, route.route, route.major);
|
|
1534
|
+
const payload = await this.#decoder.read(response);
|
|
1535
|
+
if (response.ok)
|
|
1536
|
+
return response.status === 204 ? undefined : payload;
|
|
1537
|
+
const data = this.#decoder.errorData(payload);
|
|
1538
|
+
const retryAfter = response.status === 429 ? this.#decoder.retryAfter(response, data) : undefined;
|
|
1539
|
+
if (data.global && retryAfter !== undefined)
|
|
1540
|
+
await this.#limiter.noteGlobalReset(Date.now() + retryAfter * 1000);
|
|
1541
|
+
if (response.status === 429)
|
|
1542
|
+
this.#emit(this.#hooks.onRateLimit, {
|
|
1543
|
+
method,
|
|
1544
|
+
path,
|
|
1545
|
+
retryAfterMs: (retryAfter ?? 1) * 1000,
|
|
1546
|
+
global: data.global ?? false,
|
|
1547
|
+
bucket: await this.#limiter.store.getBucketHash(route.route)
|
|
1548
|
+
});
|
|
1549
|
+
if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, response.status)) {
|
|
1550
|
+
const delay = this.#retryPolicy.getDelay(attempt, retryAfter);
|
|
1551
|
+
this.#emit(this.#hooks.onRetry, {
|
|
1552
|
+
method,
|
|
1553
|
+
path,
|
|
1554
|
+
attempt,
|
|
1555
|
+
status: response.status,
|
|
1556
|
+
delayMs: delay
|
|
1557
|
+
});
|
|
1558
|
+
await sleep(delay, options.signal, path);
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
throw new RESTError(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method, path });
|
|
1100
1562
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
if (
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1563
|
+
throw new RESTError("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method, path });
|
|
1564
|
+
}
|
|
1565
|
+
#headers(body, options) {
|
|
1566
|
+
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
1567
|
+
const headers = { ...options.headers };
|
|
1568
|
+
if (options.auth !== false && this.#token)
|
|
1569
|
+
headers["Authorization"] = `Bot ${this.#token}`;
|
|
1570
|
+
headers["User-Agent"] = USER_AGENT;
|
|
1571
|
+
if (options.reason !== undefined)
|
|
1572
|
+
headers["X-Audit-Log-Reason"] = encodeURIComponent(options.reason);
|
|
1573
|
+
if (!isFormData)
|
|
1574
|
+
headers["Content-Type"] = "application/json";
|
|
1575
|
+
return headers;
|
|
1576
|
+
}
|
|
1577
|
+
#encodeBody(body) {
|
|
1578
|
+
if (typeof FormData !== "undefined" && body instanceof FormData)
|
|
1579
|
+
return body;
|
|
1580
|
+
return body === undefined ? undefined : JSON.stringify(body);
|
|
1581
|
+
}
|
|
1582
|
+
#dispatch(method, path, bodyOrOptions, options) {
|
|
1583
|
+
if (options === undefined && isRequestData(bodyOrOptions))
|
|
1584
|
+
return this.request(method, path, bodyOrOptions.body, bodyOrOptions);
|
|
1585
|
+
return this.request(method, path, bodyOrOptions, options);
|
|
1586
|
+
}
|
|
1587
|
+
get(path, options) {
|
|
1588
|
+
return this.request("GET", path, options?.body, options);
|
|
1589
|
+
}
|
|
1590
|
+
post(path, body, options) {
|
|
1591
|
+
return this.#dispatch("POST", path, body, options);
|
|
1592
|
+
}
|
|
1593
|
+
patch(path, body, options) {
|
|
1594
|
+
return this.#dispatch("PATCH", path, body, options);
|
|
1595
|
+
}
|
|
1596
|
+
put(path, body, options) {
|
|
1597
|
+
return this.#dispatch("PUT", path, body, options);
|
|
1598
|
+
}
|
|
1599
|
+
delete(path, options) {
|
|
1600
|
+
return this.request("DELETE", path, options?.body, options);
|
|
1601
|
+
}
|
|
1602
|
+
#fileForm(payload, files) {
|
|
1603
|
+
const form = new FormData;
|
|
1604
|
+
form.append("payload_json", JSON.stringify(payload));
|
|
1605
|
+
for (let i = 0;i < files.length; i++) {
|
|
1606
|
+
const file = files[i];
|
|
1607
|
+
const blob = file.data instanceof Blob ? file.data : new Blob([file.data], {
|
|
1608
|
+
type: file.contentType ?? "application/octet-stream"
|
|
1609
|
+
});
|
|
1610
|
+
form.append(`files[${i}]`, blob, file.name);
|
|
1119
1611
|
}
|
|
1120
|
-
return
|
|
1612
|
+
return form;
|
|
1613
|
+
}
|
|
1614
|
+
postWithFiles(path, payload, files, options) {
|
|
1615
|
+
return this.request("POST", path, this.#fileForm(payload, files), options);
|
|
1616
|
+
}
|
|
1617
|
+
patchWithFiles(path, payload, files, options) {
|
|
1618
|
+
return this.request("PATCH", path, this.#fileForm(payload, files), options);
|
|
1619
|
+
}
|
|
1620
|
+
#emit(hook, ctx) {
|
|
1621
|
+
if (!hook)
|
|
1622
|
+
return;
|
|
1623
|
+
Promise.resolve().then(() => hook(ctx)).catch(() => {});
|
|
1121
1624
|
}
|
|
1122
1625
|
}
|
|
1123
1626
|
|
|
@@ -1240,6 +1743,18 @@ class Channel extends BaseStructure {
|
|
|
1240
1743
|
update(options) {
|
|
1241
1744
|
return this.edit(options);
|
|
1242
1745
|
}
|
|
1746
|
+
isThread() {
|
|
1747
|
+
return this.type === 10 || this.type === 11 || this.type === 12;
|
|
1748
|
+
}
|
|
1749
|
+
isTextBased() {
|
|
1750
|
+
return [0, 1, 2, 3, 5, 10, 11, 12, 13].includes(this.type);
|
|
1751
|
+
}
|
|
1752
|
+
isVoiceBased() {
|
|
1753
|
+
return this.type === 2 || this.type === 13;
|
|
1754
|
+
}
|
|
1755
|
+
isDMBased() {
|
|
1756
|
+
return this.type === 1 || this.type === 3;
|
|
1757
|
+
}
|
|
1243
1758
|
toString() {
|
|
1244
1759
|
return `<#${this.id}>`;
|
|
1245
1760
|
}
|
|
@@ -1263,6 +1778,13 @@ class Guild extends BaseStructure {
|
|
|
1263
1778
|
vanityUrlCode;
|
|
1264
1779
|
nsfwLevel;
|
|
1265
1780
|
discoverySplash;
|
|
1781
|
+
rulesChannelId;
|
|
1782
|
+
systemChannelId;
|
|
1783
|
+
systemChannelFlags;
|
|
1784
|
+
maxMembers;
|
|
1785
|
+
mfaLevel;
|
|
1786
|
+
explicitContentFilter;
|
|
1787
|
+
defaultMessageNotifications;
|
|
1266
1788
|
constructor(data) {
|
|
1267
1789
|
super(data.id);
|
|
1268
1790
|
if (!data.name)
|
|
@@ -1284,6 +1806,14 @@ class Guild extends BaseStructure {
|
|
|
1284
1806
|
this.vanityUrlCode = data.vanity_url_code ?? null;
|
|
1285
1807
|
this.nsfwLevel = data.nsfw_level ?? 0;
|
|
1286
1808
|
this.discoverySplash = data.discovery_splash ?? null;
|
|
1809
|
+
const d = data;
|
|
1810
|
+
this.rulesChannelId = d.rules_channel_id ?? null;
|
|
1811
|
+
this.systemChannelId = d.system_channel_id ?? null;
|
|
1812
|
+
this.systemChannelFlags = d.system_channel_flags ?? 0;
|
|
1813
|
+
this.maxMembers = d.max_members ?? 0;
|
|
1814
|
+
this.mfaLevel = d.mfa_level ?? 0;
|
|
1815
|
+
this.explicitContentFilter = d.explicit_content_filter ?? 0;
|
|
1816
|
+
this.defaultMessageNotifications = d.default_message_notifications ?? 0;
|
|
1287
1817
|
}
|
|
1288
1818
|
iconURL(options = {}) {
|
|
1289
1819
|
if (!this.icon)
|
|
@@ -1305,12 +1835,27 @@ class Guild extends BaseStructure {
|
|
|
1305
1835
|
return null;
|
|
1306
1836
|
return cdnURL(`/discovery-splashes/${this.id}`, this.discoverySplash, options);
|
|
1307
1837
|
}
|
|
1838
|
+
widgetImageURL(style = "shield") {
|
|
1839
|
+
return `https://discord.com/api/guilds/${this.id}/widget.png?style=${style}`;
|
|
1840
|
+
}
|
|
1308
1841
|
hasFeature(feature) {
|
|
1309
1842
|
return this.features.includes(feature);
|
|
1310
1843
|
}
|
|
1311
1844
|
get isCommunity() {
|
|
1312
1845
|
return this.hasFeature("COMMUNITY");
|
|
1313
1846
|
}
|
|
1847
|
+
get isPartnered() {
|
|
1848
|
+
return this.hasFeature("PARTNERED");
|
|
1849
|
+
}
|
|
1850
|
+
get isVerified() {
|
|
1851
|
+
return this.hasFeature("VERIFIED");
|
|
1852
|
+
}
|
|
1853
|
+
get isBoosted() {
|
|
1854
|
+
return this.premiumSubscriptionCount > 0;
|
|
1855
|
+
}
|
|
1856
|
+
get boostLevel() {
|
|
1857
|
+
return this.premiumTier;
|
|
1858
|
+
}
|
|
1314
1859
|
get hasVanityUrl() {
|
|
1315
1860
|
return this.vanityUrlCode !== null;
|
|
1316
1861
|
}
|
|
@@ -1321,66 +1866,158 @@ class Guild extends BaseStructure {
|
|
|
1321
1866
|
return this.name;
|
|
1322
1867
|
}
|
|
1323
1868
|
}
|
|
1324
|
-
// packages/structures/src/
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1869
|
+
// packages/structures/src/resources.ts
|
|
1870
|
+
class TextChannel extends Channel {
|
|
1871
|
+
constructor(data, context) {
|
|
1872
|
+
super(data, context);
|
|
1873
|
+
}
|
|
1874
|
+
toString() {
|
|
1875
|
+
return `<#${this.id}>`;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
class AutoModerationRule extends BaseStructure {
|
|
1879
|
+
guildId;
|
|
1880
|
+
name;
|
|
1881
|
+
creatorId;
|
|
1882
|
+
eventType;
|
|
1883
|
+
triggerType;
|
|
1884
|
+
triggerMetadata;
|
|
1885
|
+
actions;
|
|
1886
|
+
enabled;
|
|
1887
|
+
exemptRoles;
|
|
1888
|
+
exemptChannels;
|
|
1889
|
+
constructor(data) {
|
|
1890
|
+
super(data.id);
|
|
1891
|
+
if (!/^\d{1,20}$/.test(data.guild_id))
|
|
1892
|
+
throw new TypeError("AutoModerationRule guild_id must be a valid snowflake.");
|
|
1893
|
+
this.guildId = data.guild_id;
|
|
1894
|
+
this.name = data.name;
|
|
1895
|
+
this.creatorId = data.creator_id;
|
|
1896
|
+
this.eventType = data.event_type;
|
|
1897
|
+
this.triggerType = data.trigger_type;
|
|
1898
|
+
this.triggerMetadata = data.trigger_metadata;
|
|
1899
|
+
this.actions = data.actions;
|
|
1900
|
+
this.enabled = data.enabled;
|
|
1901
|
+
this.exemptRoles = data.exempt_roles;
|
|
1902
|
+
this.exemptChannels = data.exempt_channels;
|
|
1903
|
+
}
|
|
1904
|
+
toJSON() {
|
|
1905
|
+
return {
|
|
1906
|
+
id: this.id,
|
|
1907
|
+
guildId: this.guildId,
|
|
1908
|
+
name: this.name,
|
|
1909
|
+
creatorId: this.creatorId,
|
|
1910
|
+
eventType: this.eventType,
|
|
1911
|
+
triggerType: this.triggerType,
|
|
1912
|
+
triggerMetadata: this.triggerMetadata,
|
|
1913
|
+
actions: this.actions,
|
|
1914
|
+
enabled: this.enabled,
|
|
1915
|
+
exemptRoles: this.exemptRoles,
|
|
1916
|
+
exemptChannels: this.exemptChannels
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
// packages/structures/src/channels.ts
|
|
1921
|
+
class NewsChannel extends TextChannel {
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
class DMChannel extends Channel {
|
|
1925
|
+
recipients;
|
|
1926
|
+
ownerId;
|
|
1927
|
+
constructor(data, context) {
|
|
1928
|
+
super(data, context);
|
|
1929
|
+
this.recipients = (data.recipients ?? []).map((u) => new User(u));
|
|
1930
|
+
this.ownerId = data.owner_id ?? null;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
class VoiceChannel extends Channel {
|
|
1935
|
+
bitrate;
|
|
1936
|
+
userLimit;
|
|
1937
|
+
rtcRegion;
|
|
1938
|
+
videoQualityMode;
|
|
1939
|
+
constructor(data, context) {
|
|
1940
|
+
super(data, context);
|
|
1941
|
+
this.bitrate = data.bitrate ?? 64000;
|
|
1942
|
+
this.userLimit = data.user_limit ?? 0;
|
|
1943
|
+
this.rtcRegion = data.rtc_region ?? null;
|
|
1944
|
+
this.videoQualityMode = data.video_quality_mode ?? 1;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
class StageChannel extends VoiceChannel {
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
class CategoryChannel extends Channel {
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
class ThreadChannel extends Channel {
|
|
1955
|
+
ownerId;
|
|
1956
|
+
messageCount;
|
|
1957
|
+
memberCount;
|
|
1958
|
+
metadata;
|
|
1959
|
+
constructor(data, context) {
|
|
1960
|
+
super(data, context);
|
|
1961
|
+
this.ownerId = data.owner_id ?? null;
|
|
1962
|
+
this.messageCount = data.message_count ?? 0;
|
|
1963
|
+
this.memberCount = data.member_count ?? 0;
|
|
1964
|
+
this.metadata = data.thread_metadata ?? null;
|
|
1965
|
+
}
|
|
1966
|
+
get archived() {
|
|
1967
|
+
return this.metadata?.archived ?? false;
|
|
1968
|
+
}
|
|
1969
|
+
get locked() {
|
|
1970
|
+
return this.metadata?.locked ?? false;
|
|
1971
|
+
}
|
|
1972
|
+
get autoArchiveDuration() {
|
|
1973
|
+
return this.metadata?.auto_archive_duration ?? null;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
class ForumChannel extends Channel {
|
|
1978
|
+
availableTags;
|
|
1979
|
+
defaultReactionEmoji;
|
|
1980
|
+
defaultThreadRateLimitPerUser;
|
|
1981
|
+
defaultAutoArchiveDuration;
|
|
1982
|
+
constructor(data, context) {
|
|
1983
|
+
super(data, context);
|
|
1984
|
+
this.availableTags = data.available_tags ?? [];
|
|
1985
|
+
this.defaultReactionEmoji = data.default_reaction_emoji ?? null;
|
|
1986
|
+
this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user ?? 0;
|
|
1987
|
+
this.defaultAutoArchiveDuration = data.default_auto_archive_duration ?? null;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
class MediaChannel extends ForumChannel {
|
|
1992
|
+
}
|
|
1993
|
+
function createChannel(data, context) {
|
|
1994
|
+
switch (data.type) {
|
|
1995
|
+
case 0:
|
|
1996
|
+
return new TextChannel(data, context);
|
|
1997
|
+
case 1:
|
|
1998
|
+
case 3:
|
|
1999
|
+
return new DMChannel(data, context);
|
|
2000
|
+
case 2:
|
|
2001
|
+
return new VoiceChannel(data, context);
|
|
2002
|
+
case 4:
|
|
2003
|
+
return new CategoryChannel(data, context);
|
|
2004
|
+
case 5:
|
|
2005
|
+
return new NewsChannel(data, context);
|
|
2006
|
+
case 10:
|
|
2007
|
+
case 11:
|
|
2008
|
+
case 12:
|
|
2009
|
+
return new ThreadChannel(data, context);
|
|
2010
|
+
case 13:
|
|
2011
|
+
return new StageChannel(data, context);
|
|
2012
|
+
case 15:
|
|
2013
|
+
return new ForumChannel(data, context);
|
|
2014
|
+
case 16:
|
|
2015
|
+
return new MediaChannel(data, context);
|
|
2016
|
+
default:
|
|
2017
|
+
return new Channel(data, context);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
// packages/structures/src/options.ts
|
|
1384
2021
|
var OptionType = {
|
|
1385
2022
|
SubCommand: 1,
|
|
1386
2023
|
SubCommandGroup: 2,
|
|
@@ -1436,6 +2073,12 @@ class CommandOptions {
|
|
|
1436
2073
|
}
|
|
1437
2074
|
return this.#subcommandGroup;
|
|
1438
2075
|
}
|
|
2076
|
+
getFocused(full = false) {
|
|
2077
|
+
const focused = this.#options.find((o) => o.focused);
|
|
2078
|
+
if (!focused)
|
|
2079
|
+
throw new TypeError("No focused option was provided.");
|
|
2080
|
+
return full ? focused : focused.value ?? "";
|
|
2081
|
+
}
|
|
1439
2082
|
getString(name, required = false) {
|
|
1440
2083
|
const opt = this.#get(name);
|
|
1441
2084
|
if (!opt || opt.type !== OptionType.String) {
|
|
@@ -1541,6 +2184,14 @@ class CommandOptions {
|
|
|
1541
2184
|
return this.#options;
|
|
1542
2185
|
}
|
|
1543
2186
|
}
|
|
2187
|
+
// packages/structures/src/interactions.ts
|
|
2188
|
+
var InteractionType = {
|
|
2189
|
+
Ping: 1,
|
|
2190
|
+
ApplicationCommand: 2,
|
|
2191
|
+
MessageComponent: 3,
|
|
2192
|
+
ApplicationCommandAutocomplete: 4,
|
|
2193
|
+
ModalSubmit: 5
|
|
2194
|
+
};
|
|
1544
2195
|
|
|
1545
2196
|
class InteractionResponse {
|
|
1546
2197
|
type;
|
|
@@ -1604,6 +2255,30 @@ class Interaction {
|
|
|
1604
2255
|
isAutocomplete() {
|
|
1605
2256
|
return this.type === InteractionType.ApplicationCommandAutocomplete;
|
|
1606
2257
|
}
|
|
2258
|
+
#componentType() {
|
|
2259
|
+
return this.isMessageComponent() ? this.data.data?.component_type ?? 0 : 0;
|
|
2260
|
+
}
|
|
2261
|
+
isButton() {
|
|
2262
|
+
return this.#componentType() === 2;
|
|
2263
|
+
}
|
|
2264
|
+
isStringSelectMenu() {
|
|
2265
|
+
return this.#componentType() === 3;
|
|
2266
|
+
}
|
|
2267
|
+
isUserSelectMenu() {
|
|
2268
|
+
return this.#componentType() === 5;
|
|
2269
|
+
}
|
|
2270
|
+
isRoleSelectMenu() {
|
|
2271
|
+
return this.#componentType() === 6;
|
|
2272
|
+
}
|
|
2273
|
+
isMentionableSelectMenu() {
|
|
2274
|
+
return this.#componentType() === 7;
|
|
2275
|
+
}
|
|
2276
|
+
isChannelSelectMenu() {
|
|
2277
|
+
return this.#componentType() === 8;
|
|
2278
|
+
}
|
|
2279
|
+
isAnySelectMenu() {
|
|
2280
|
+
return [3, 5, 6, 7, 8].includes(this.#componentType());
|
|
2281
|
+
}
|
|
1607
2282
|
assertUnacknowledged() {
|
|
1608
2283
|
if (this.replied || this.deferred)
|
|
1609
2284
|
throw new Error("Interaction has already been acknowledged.");
|
|
@@ -1663,8 +2338,7 @@ class CommandInteraction extends Interaction {
|
|
|
1663
2338
|
super(client, data);
|
|
1664
2339
|
const rawOptions = data.data?.options ?? [];
|
|
1665
2340
|
const resolved = data.data?.resolved ?? {};
|
|
1666
|
-
|
|
1667
|
-
this.options = new CommandOptions(sub?.options ?? rawOptions, resolved);
|
|
2341
|
+
this.options = new CommandOptions(rawOptions, resolved);
|
|
1668
2342
|
}
|
|
1669
2343
|
}
|
|
1670
2344
|
|
|
@@ -1715,6 +2389,11 @@ class ModalSubmitInteraction extends Interaction {
|
|
|
1715
2389
|
}
|
|
1716
2390
|
|
|
1717
2391
|
class AutocompleteInteraction extends Interaction {
|
|
2392
|
+
options;
|
|
2393
|
+
constructor(client, data) {
|
|
2394
|
+
super(client, data);
|
|
2395
|
+
this.options = new CommandOptions(data.data?.options ?? []);
|
|
2396
|
+
}
|
|
1718
2397
|
get commandName() {
|
|
1719
2398
|
return this.data?.data?.name ?? "";
|
|
1720
2399
|
}
|
|
@@ -1758,6 +2437,8 @@ function createInteraction(client, data) {
|
|
|
1758
2437
|
}
|
|
1759
2438
|
|
|
1760
2439
|
// packages/structures/src/index.ts
|
|
2440
|
+
var NON_SYSTEM_MESSAGE_TYPES = new Set([0, 19, 20, 23]);
|
|
2441
|
+
|
|
1761
2442
|
class Message extends BaseStructure {
|
|
1762
2443
|
content;
|
|
1763
2444
|
author;
|
|
@@ -1803,6 +2484,12 @@ class Message extends BaseStructure {
|
|
|
1803
2484
|
get embedsSuppressed() {
|
|
1804
2485
|
return (this.flags & 4) !== 0;
|
|
1805
2486
|
}
|
|
2487
|
+
get createdTimestamp() {
|
|
2488
|
+
return this.timestamp.getTime();
|
|
2489
|
+
}
|
|
2490
|
+
get createdAt() {
|
|
2491
|
+
return this.timestamp;
|
|
2492
|
+
}
|
|
1806
2493
|
edit(options) {
|
|
1807
2494
|
if (!this.#context)
|
|
1808
2495
|
throw new Error("This message is not attached to a client.");
|
|
@@ -1857,6 +2544,51 @@ class Message extends BaseStructure {
|
|
|
1857
2544
|
throw new Error("This message is not attached to a client.");
|
|
1858
2545
|
return this.#context.unpinMessage(this.channelId, this.id);
|
|
1859
2546
|
}
|
|
2547
|
+
get isEphemeral() {
|
|
2548
|
+
return (this.flags & 64) !== 0;
|
|
2549
|
+
}
|
|
2550
|
+
get isPinned() {
|
|
2551
|
+
return this.pinned;
|
|
2552
|
+
}
|
|
2553
|
+
get isSystemMessage() {
|
|
2554
|
+
return !NON_SYSTEM_MESSAGE_TYPES.has(this.type);
|
|
2555
|
+
}
|
|
2556
|
+
get isCrosspost() {
|
|
2557
|
+
return (this.flags & 2) !== 0;
|
|
2558
|
+
}
|
|
2559
|
+
get hasAttachments() {
|
|
2560
|
+
return this.attachments.length > 0;
|
|
2561
|
+
}
|
|
2562
|
+
get hasEmbeds() {
|
|
2563
|
+
return this.embeds.length > 0;
|
|
2564
|
+
}
|
|
2565
|
+
get hasComponents() {
|
|
2566
|
+
return this.components.length > 0;
|
|
2567
|
+
}
|
|
2568
|
+
suppressEmbeds() {
|
|
2569
|
+
return this.edit({ flags: this.flags | 4 });
|
|
2570
|
+
}
|
|
2571
|
+
toJSON() {
|
|
2572
|
+
return {
|
|
2573
|
+
id: this.id,
|
|
2574
|
+
content: this.content,
|
|
2575
|
+
channelId: this.channelId,
|
|
2576
|
+
guildId: this.guildId,
|
|
2577
|
+
flags: this.flags,
|
|
2578
|
+
pinned: this.pinned,
|
|
2579
|
+
mentionEveryone: this.mentionEveryone,
|
|
2580
|
+
type: this.type,
|
|
2581
|
+
timestamp: this.timestamp.toISOString(),
|
|
2582
|
+
author: { id: this.author.id, username: this.author.username },
|
|
2583
|
+
attachments: [...this.attachments],
|
|
2584
|
+
embeds: [...this.embeds],
|
|
2585
|
+
components: [...this.components],
|
|
2586
|
+
mentions: this.mentions.map((u) => u.id),
|
|
2587
|
+
mentionRoles: [...this.mentionRoles],
|
|
2588
|
+
reference: this.reference,
|
|
2589
|
+
referencedMessage: this.referencedMessage ? this.referencedMessage.id : this.referencedMessage
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
1860
2592
|
}
|
|
1861
2593
|
|
|
1862
2594
|
// packages/managers/src/message.ts
|
|
@@ -1919,7 +2651,7 @@ class ThreadManager {
|
|
|
1919
2651
|
auto_archive_duration: options.autoArchiveDuration,
|
|
1920
2652
|
rate_limit_per_user: options.rateLimitPerUser
|
|
1921
2653
|
});
|
|
1922
|
-
return
|
|
2654
|
+
return createChannel(data, this.#context);
|
|
1923
2655
|
}
|
|
1924
2656
|
}
|
|
1925
2657
|
|
|
@@ -1947,13 +2679,173 @@ class UserManager extends ResourceManager {
|
|
|
1947
2679
|
});
|
|
1948
2680
|
}
|
|
1949
2681
|
}
|
|
2682
|
+
// packages/managers/src/guild-resources.ts
|
|
2683
|
+
function withQuery(path, params) {
|
|
2684
|
+
const query = new URLSearchParams;
|
|
2685
|
+
for (const [key, value] of Object.entries(params))
|
|
2686
|
+
if (value !== undefined)
|
|
2687
|
+
query.set(key, String(value));
|
|
2688
|
+
const suffix = query.toString();
|
|
2689
|
+
return suffix ? `${path}?${suffix}` : path;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
class GuildBanManager extends Manager {
|
|
2693
|
+
#rest;
|
|
2694
|
+
guildId;
|
|
2695
|
+
constructor(rest, guildId) {
|
|
2696
|
+
super();
|
|
2697
|
+
this.#rest = rest;
|
|
2698
|
+
this.guildId = guildId;
|
|
2699
|
+
}
|
|
2700
|
+
async fetch(userId) {
|
|
2701
|
+
const ban = await this.#rest.get(Routes.guildBan(this.guildId, userId));
|
|
2702
|
+
this.set(ban.user.id, ban);
|
|
2703
|
+
return ban;
|
|
2704
|
+
}
|
|
2705
|
+
async fetchAll(options = {}) {
|
|
2706
|
+
const limit = options.limit === undefined ? undefined : Math.min(1000, Math.max(1, options.limit));
|
|
2707
|
+
const bans = await this.#rest.get(withQuery(Routes.guildBans(this.guildId), {
|
|
2708
|
+
limit,
|
|
2709
|
+
before: options.before,
|
|
2710
|
+
after: options.after
|
|
2711
|
+
}));
|
|
2712
|
+
for (const ban of bans)
|
|
2713
|
+
this.set(ban.user.id, ban);
|
|
2714
|
+
return bans;
|
|
2715
|
+
}
|
|
2716
|
+
async create(userId, options = {}) {
|
|
2717
|
+
await this.#rest.put(Routes.guildBan(this.guildId, userId), { delete_message_seconds: options.deleteMessageSeconds }, { reason: options.reason });
|
|
2718
|
+
}
|
|
2719
|
+
async remove(userId, reason) {
|
|
2720
|
+
await this.#rest.delete(Routes.guildBan(this.guildId, userId), {
|
|
2721
|
+
reason
|
|
2722
|
+
});
|
|
2723
|
+
this.delete(userId);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
class GuildScheduledEventManager extends Manager {
|
|
2728
|
+
#rest;
|
|
2729
|
+
guildId;
|
|
2730
|
+
constructor(rest, guildId) {
|
|
2731
|
+
super();
|
|
2732
|
+
this.#rest = rest;
|
|
2733
|
+
this.guildId = guildId;
|
|
2734
|
+
}
|
|
2735
|
+
#store(event) {
|
|
2736
|
+
this.set(event.id, event);
|
|
2737
|
+
return event;
|
|
2738
|
+
}
|
|
2739
|
+
async fetch(eventId, options = {}) {
|
|
2740
|
+
return this.#store(await this.#rest.get(withQuery(Routes.guildScheduledEvent(this.guildId, eventId), {
|
|
2741
|
+
with_user_count: options.withUserCount
|
|
2742
|
+
})));
|
|
2743
|
+
}
|
|
2744
|
+
async fetchAll(options = {}) {
|
|
2745
|
+
const events = await this.#rest.get(withQuery(Routes.guildScheduledEvents(this.guildId), {
|
|
2746
|
+
with_user_count: options.withUserCount
|
|
2747
|
+
}));
|
|
2748
|
+
return events.map((event) => this.#store(event));
|
|
2749
|
+
}
|
|
2750
|
+
async create(options, reason) {
|
|
2751
|
+
return this.#store(await this.#rest.post(Routes.guildScheduledEvents(this.guildId), options, { reason }));
|
|
2752
|
+
}
|
|
2753
|
+
async edit(eventId, options, reason) {
|
|
2754
|
+
return this.#store(await this.#rest.patch(Routes.guildScheduledEvent(this.guildId, eventId), options, { reason }));
|
|
2755
|
+
}
|
|
2756
|
+
async remove(eventId) {
|
|
2757
|
+
await this.#rest.delete(Routes.guildScheduledEvent(this.guildId, eventId));
|
|
2758
|
+
this.delete(eventId);
|
|
2759
|
+
}
|
|
2760
|
+
async fetchSubscribers(eventId, options = {}) {
|
|
2761
|
+
const limit = options.limit === undefined ? undefined : Math.min(100, Math.max(1, options.limit));
|
|
2762
|
+
return this.#rest.get(withQuery(Routes.guildScheduledEventUsers(this.guildId, eventId), {
|
|
2763
|
+
limit,
|
|
2764
|
+
with_member: options.withMember,
|
|
2765
|
+
before: options.before,
|
|
2766
|
+
after: options.after
|
|
2767
|
+
}));
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
class StageInstanceManager extends Manager {
|
|
2772
|
+
#rest;
|
|
2773
|
+
constructor(rest) {
|
|
2774
|
+
super();
|
|
2775
|
+
this.#rest = rest;
|
|
2776
|
+
}
|
|
2777
|
+
#store(stage) {
|
|
2778
|
+
this.set(stage.channel_id, stage);
|
|
2779
|
+
return stage;
|
|
2780
|
+
}
|
|
2781
|
+
async create(channelId, options, reason) {
|
|
2782
|
+
return this.#store(await this.#rest.post(Routes.stageInstances(), {
|
|
2783
|
+
channel_id: channelId,
|
|
2784
|
+
topic: options.topic,
|
|
2785
|
+
privacy_level: options.privacyLevel,
|
|
2786
|
+
send_start_notification: options.sendStartNotification,
|
|
2787
|
+
guild_scheduled_event_id: options.guildScheduledEventId
|
|
2788
|
+
}, { reason }));
|
|
2789
|
+
}
|
|
2790
|
+
async fetch(channelId) {
|
|
2791
|
+
return this.#store(await this.#rest.get(Routes.stageInstanceByChannel(channelId)));
|
|
2792
|
+
}
|
|
2793
|
+
async edit(channelId, options, reason) {
|
|
2794
|
+
return this.#store(await this.#rest.patch(Routes.stageInstanceByChannel(channelId), { topic: options.topic, privacy_level: options.privacyLevel }, { reason }));
|
|
2795
|
+
}
|
|
2796
|
+
async remove(channelId, reason) {
|
|
2797
|
+
await this.#rest.delete(Routes.stageInstanceByChannel(channelId), {
|
|
2798
|
+
reason
|
|
2799
|
+
});
|
|
2800
|
+
this.delete(channelId);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
class PermissionOverwriteManager {
|
|
2805
|
+
#rest;
|
|
2806
|
+
channelId;
|
|
2807
|
+
constructor(rest, channelId) {
|
|
2808
|
+
this.#rest = rest;
|
|
2809
|
+
this.channelId = channelId;
|
|
2810
|
+
}
|
|
2811
|
+
async edit(overwriteId, options, reason) {
|
|
2812
|
+
await this.#rest.put(Routes.channelPermission(this.channelId, overwriteId), {
|
|
2813
|
+
type: options.type,
|
|
2814
|
+
allow: BigInt(options.allow ?? 0).toString(),
|
|
2815
|
+
deny: BigInt(options.deny ?? 0).toString()
|
|
2816
|
+
}, { reason });
|
|
2817
|
+
}
|
|
2818
|
+
async remove(overwriteId, reason) {
|
|
2819
|
+
await this.#rest.delete(Routes.channelPermission(this.channelId, overwriteId), { reason });
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
|
|
1950
2823
|
// packages/managers/src/guild.ts
|
|
2824
|
+
function withQuery2(path, params) {
|
|
2825
|
+
const suffix = params.toString();
|
|
2826
|
+
return suffix ? `${path}?${suffix}` : path;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
1951
2829
|
class GuildManager extends ResourceManager {
|
|
1952
2830
|
#rest;
|
|
2831
|
+
#bans = new Map;
|
|
2832
|
+
#scheduledEvents = new Map;
|
|
1953
2833
|
constructor(rest) {
|
|
1954
2834
|
super(async (id) => new Guild(await rest.get(Routes.guild(id))), (guild) => guild.id);
|
|
1955
2835
|
this.#rest = rest;
|
|
1956
2836
|
}
|
|
2837
|
+
bans(guildId) {
|
|
2838
|
+
let manager = this.#bans.get(guildId);
|
|
2839
|
+
if (!manager)
|
|
2840
|
+
this.#bans.set(guildId, manager = new GuildBanManager(this.#rest, guildId));
|
|
2841
|
+
return manager;
|
|
2842
|
+
}
|
|
2843
|
+
scheduledEvents(guildId) {
|
|
2844
|
+
let manager = this.#scheduledEvents.get(guildId);
|
|
2845
|
+
if (!manager)
|
|
2846
|
+
this.#scheduledEvents.set(guildId, manager = new GuildScheduledEventManager(this.#rest, guildId));
|
|
2847
|
+
return manager;
|
|
2848
|
+
}
|
|
1957
2849
|
async create(options) {
|
|
1958
2850
|
const data = await this.#rest.post("/guilds", options);
|
|
1959
2851
|
return this.upsert(new Guild(data));
|
|
@@ -1977,6 +2869,47 @@ class GuildManager extends ResourceManager {
|
|
|
1977
2869
|
async fetchInvites(id) {
|
|
1978
2870
|
return this.#rest.get(Routes.guildInvites(id));
|
|
1979
2871
|
}
|
|
2872
|
+
async fetchAuditLog(guildId, options = {}) {
|
|
2873
|
+
const params = new URLSearchParams;
|
|
2874
|
+
if (options.userId)
|
|
2875
|
+
params.set("user_id", options.userId);
|
|
2876
|
+
if (options.actionType !== undefined)
|
|
2877
|
+
params.set("action_type", String(options.actionType));
|
|
2878
|
+
if (options.before)
|
|
2879
|
+
params.set("before", options.before);
|
|
2880
|
+
if (options.after)
|
|
2881
|
+
params.set("after", options.after);
|
|
2882
|
+
if (options.limit !== undefined)
|
|
2883
|
+
params.set("limit", String(Math.min(100, Math.max(1, options.limit))));
|
|
2884
|
+
return this.#rest.get(withQuery2(Routes.guildAuditLog(guildId), params));
|
|
2885
|
+
}
|
|
2886
|
+
async fetchMembers(guildId, options = {}) {
|
|
2887
|
+
const params = new URLSearchParams;
|
|
2888
|
+
if (options.limit !== undefined)
|
|
2889
|
+
params.set("limit", String(Math.min(1000, Math.max(1, options.limit))));
|
|
2890
|
+
if (options.after)
|
|
2891
|
+
params.set("after", options.after);
|
|
2892
|
+
return this.#rest.get(withQuery2(Routes.guildMembers(guildId), params));
|
|
2893
|
+
}
|
|
2894
|
+
async fetchAutoModerationRules(guildId) {
|
|
2895
|
+
const rules = await this.#rest.get(Routes.guildAutoModerationRules(guildId));
|
|
2896
|
+
return rules.map((r) => new AutoModerationRule(r));
|
|
2897
|
+
}
|
|
2898
|
+
async fetchAutoModerationRule(guildId, ruleId) {
|
|
2899
|
+
const rule = await this.#rest.get(Routes.guildAutoModerationRule(guildId, ruleId));
|
|
2900
|
+
return new AutoModerationRule(rule);
|
|
2901
|
+
}
|
|
2902
|
+
async createAutoModerationRule(guildId, options) {
|
|
2903
|
+
const rule = await this.#rest.post(Routes.guildAutoModerationRules(guildId), options);
|
|
2904
|
+
return new AutoModerationRule(rule);
|
|
2905
|
+
}
|
|
2906
|
+
async editAutoModerationRule(guildId, ruleId, options) {
|
|
2907
|
+
const rule = await this.#rest.patch(Routes.guildAutoModerationRule(guildId, ruleId), options);
|
|
2908
|
+
return new AutoModerationRule(rule);
|
|
2909
|
+
}
|
|
2910
|
+
async deleteAutoModerationRule(guildId, ruleId) {
|
|
2911
|
+
await this.#rest.delete(Routes.guildAutoModerationRule(guildId, ruleId));
|
|
2912
|
+
}
|
|
1980
2913
|
}
|
|
1981
2914
|
// packages/managers/src/application.ts
|
|
1982
2915
|
class ApplicationCommandManager {
|
|
@@ -2049,6 +2982,9 @@ class ChannelManager extends Manager {
|
|
|
2049
2982
|
}
|
|
2050
2983
|
return manager;
|
|
2051
2984
|
}
|
|
2985
|
+
permissionOverwrites(channelId) {
|
|
2986
|
+
return new PermissionOverwriteManager(this.#rest, channelId);
|
|
2987
|
+
}
|
|
2052
2988
|
threads(channelId) {
|
|
2053
2989
|
return new ThreadManager(this.#rest, this.#context, channelId);
|
|
2054
2990
|
}
|
|
@@ -2060,7 +2996,7 @@ class ChannelManager extends Manager {
|
|
|
2060
2996
|
}
|
|
2061
2997
|
upsert(data) {
|
|
2062
2998
|
const existing = this.get(data.id);
|
|
2063
|
-
const channel =
|
|
2999
|
+
const channel = createChannel(data, this.#context);
|
|
2064
3000
|
if (existing) {
|
|
2065
3001
|
Object.assign(existing, channel);
|
|
2066
3002
|
return existing;
|
|
@@ -2079,8 +3015,11 @@ class ChannelManager extends Manager {
|
|
|
2079
3015
|
}
|
|
2080
3016
|
async deleteChannel(channelId) {
|
|
2081
3017
|
await this.#rest.delete(Routes.channel(channelId));
|
|
3018
|
+
this.delete(channelId);
|
|
3019
|
+
}
|
|
3020
|
+
delete(channelId) {
|
|
2082
3021
|
this.#messageManagers.delete(channelId);
|
|
2083
|
-
super.delete(channelId);
|
|
3022
|
+
return super.delete(channelId);
|
|
2084
3023
|
}
|
|
2085
3024
|
send(channelId, options) {
|
|
2086
3025
|
return this.messages(channelId).send(options);
|
|
@@ -2120,66 +3059,638 @@ class ChannelManager extends Manager {
|
|
|
2120
3059
|
deleteCachedMessage(channelId, messageId) {
|
|
2121
3060
|
return this.messages(channelId).delete(messageId);
|
|
2122
3061
|
}
|
|
2123
|
-
async crosspostMessage(channelId, messageId) {
|
|
2124
|
-
return this.messages(channelId).upsert(await this.#rest.post(Routes.crosspostMessage(channelId, messageId)));
|
|
3062
|
+
async crosspostMessage(channelId, messageId) {
|
|
3063
|
+
return this.messages(channelId).upsert(await this.#rest.post(Routes.crosspostMessage(channelId, messageId)));
|
|
3064
|
+
}
|
|
3065
|
+
async bulkDeleteMessages(channelId, messageIds) {
|
|
3066
|
+
const ids = [...messageIds];
|
|
3067
|
+
if (ids.length < 2 || ids.length > 100)
|
|
3068
|
+
throw new RangeError("bulkDelete requires between 2 and 100 message IDs.");
|
|
3069
|
+
await this.#rest.post(Routes.channelBulkDelete(channelId), {
|
|
3070
|
+
messages: ids
|
|
3071
|
+
});
|
|
3072
|
+
for (const id of ids)
|
|
3073
|
+
this.messages(channelId).delete(id);
|
|
3074
|
+
}
|
|
3075
|
+
async addReaction(channelId, messageId, emoji) {
|
|
3076
|
+
await this.#rest.put(`${Routes.messageReactions(channelId, messageId, emoji)}/@me`);
|
|
3077
|
+
}
|
|
3078
|
+
async fetchReactions(channelId, messageId, emoji, options = {}) {
|
|
3079
|
+
const params = new URLSearchParams;
|
|
3080
|
+
if (options.limit !== undefined)
|
|
3081
|
+
params.set("limit", String(options.limit));
|
|
3082
|
+
if (options.after)
|
|
3083
|
+
params.set("after", options.after);
|
|
3084
|
+
const suffix = params.toString();
|
|
3085
|
+
const data = await this.#rest.get(`${Routes.messageReactions(channelId, messageId, emoji)}${suffix ? `?${suffix}` : ""}`);
|
|
3086
|
+
return data.map((user) => new User(user));
|
|
3087
|
+
}
|
|
3088
|
+
async removeOwnReaction(channelId, messageId, emoji) {
|
|
3089
|
+
await this.#rest.delete(`${Routes.messageReactions(channelId, messageId, emoji)}/@me`);
|
|
3090
|
+
}
|
|
3091
|
+
async removeReaction(channelId, messageId, emoji, userId) {
|
|
3092
|
+
await this.#rest.delete(`${Routes.messageReactions(channelId, messageId, emoji)}/${userId}`);
|
|
3093
|
+
}
|
|
3094
|
+
async removeAllReactions(channelId, messageId) {
|
|
3095
|
+
await this.#rest.delete(Routes.messageReactionsAll(channelId, messageId));
|
|
3096
|
+
}
|
|
3097
|
+
async fetchPinnedMessages(channelId) {
|
|
3098
|
+
const data = await this.#rest.get(Routes.channelPins(channelId));
|
|
3099
|
+
return data.map((item) => this.messages(channelId).upsert(item));
|
|
3100
|
+
}
|
|
3101
|
+
async pinMessage(channelId, messageId) {
|
|
3102
|
+
await this.#rest.put(Routes.channelPin(channelId, messageId));
|
|
3103
|
+
}
|
|
3104
|
+
async unpinMessage(channelId, messageId) {
|
|
3105
|
+
await this.#rest.delete(Routes.channelPin(channelId, messageId));
|
|
3106
|
+
}
|
|
3107
|
+
createThreadFromMessage(channelId, messageId, options) {
|
|
3108
|
+
return this.threads(channelId).createFromMessage(messageId, options);
|
|
3109
|
+
}
|
|
3110
|
+
async fetchPage(channelId, options = {}) {
|
|
3111
|
+
const limit = Math.min(100, Math.max(1, options.limit ?? 50));
|
|
3112
|
+
const messages = await this.fetchMessages(channelId, {
|
|
3113
|
+
...options,
|
|
3114
|
+
limit
|
|
3115
|
+
});
|
|
3116
|
+
return {
|
|
3117
|
+
messages,
|
|
3118
|
+
before: messages[0]?.id,
|
|
3119
|
+
after: messages[messages.length - 1]?.id,
|
|
3120
|
+
hasMore: messages.length === limit
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
async bulkDelete(channelId, messageIds) {
|
|
3124
|
+
return this.bulkDeleteMessages(channelId, messageIds);
|
|
3125
|
+
}
|
|
3126
|
+
clear() {
|
|
3127
|
+
this.#messageManagers.clear();
|
|
3128
|
+
super.clear();
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
function isMessageQuery(value) {
|
|
3132
|
+
return typeof value === "object" && value !== null && !(Symbol.iterator in value);
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
// packages/ws/src/session.ts
|
|
3136
|
+
class GatewaySession {
|
|
3137
|
+
#state = { status: "none" };
|
|
3138
|
+
#sequence = null;
|
|
3139
|
+
#generation = 0;
|
|
3140
|
+
#lastInvalidation;
|
|
3141
|
+
get lastInvalidation() {
|
|
3142
|
+
return this.#lastInvalidation;
|
|
3143
|
+
}
|
|
3144
|
+
get generation() {
|
|
3145
|
+
return this.#generation;
|
|
3146
|
+
}
|
|
3147
|
+
get sessionId() {
|
|
3148
|
+
return this.#state.status === "established" ? this.#state.sessionId : undefined;
|
|
3149
|
+
}
|
|
3150
|
+
get resumeURL() {
|
|
3151
|
+
return this.#state.status === "established" ? this.#state.resumeURL : undefined;
|
|
3152
|
+
}
|
|
3153
|
+
get sequence() {
|
|
3154
|
+
return this.#sequence;
|
|
3155
|
+
}
|
|
3156
|
+
get canResume() {
|
|
3157
|
+
return this.#state.status === "established" && this.#sequence !== null;
|
|
3158
|
+
}
|
|
3159
|
+
beginConnection() {
|
|
3160
|
+
return ++this.#generation;
|
|
3161
|
+
}
|
|
3162
|
+
owns(token) {
|
|
3163
|
+
return token === this.#generation;
|
|
3164
|
+
}
|
|
3165
|
+
recordSequence(sequence, token) {
|
|
3166
|
+
if (!this.owns(token))
|
|
3167
|
+
return false;
|
|
3168
|
+
if (!Number.isInteger(sequence))
|
|
3169
|
+
return false;
|
|
3170
|
+
this.#sequence = sequence;
|
|
3171
|
+
return true;
|
|
3172
|
+
}
|
|
3173
|
+
activate(payload, token) {
|
|
3174
|
+
if (!this.owns(token))
|
|
3175
|
+
return false;
|
|
3176
|
+
const ready = payload;
|
|
3177
|
+
if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string")
|
|
3178
|
+
return false;
|
|
3179
|
+
this.#state = {
|
|
3180
|
+
status: "established",
|
|
3181
|
+
sessionId: ready.session_id,
|
|
3182
|
+
resumeURL: ready.resume_gateway_url
|
|
3183
|
+
};
|
|
3184
|
+
return true;
|
|
3185
|
+
}
|
|
3186
|
+
invalidate(token, reason = "explicit") {
|
|
3187
|
+
if (!this.owns(token))
|
|
3188
|
+
return false;
|
|
3189
|
+
this.#lastInvalidation = reason;
|
|
3190
|
+
this.#state = { status: "none" };
|
|
3191
|
+
this.#sequence = null;
|
|
3192
|
+
return true;
|
|
3193
|
+
}
|
|
3194
|
+
resumeInfo() {
|
|
3195
|
+
if (this.#state.status !== "established")
|
|
3196
|
+
return;
|
|
3197
|
+
if (this.#sequence === null)
|
|
3198
|
+
return;
|
|
3199
|
+
const { sessionId, resumeURL } = this.#state;
|
|
3200
|
+
return { sessionId, sequence: this.#sequence, resumeURL };
|
|
3201
|
+
}
|
|
3202
|
+
handshake() {
|
|
3203
|
+
const resume = this.resumeInfo();
|
|
3204
|
+
return resume ? { type: "resume", ...resume } : { type: "identify" };
|
|
3205
|
+
}
|
|
3206
|
+
connectURL(fallback) {
|
|
3207
|
+
return this.resumeInfo()?.resumeURL ?? fallback;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
// packages/ws/src/heartbeat.ts
|
|
3212
|
+
var MAX_MONITOR_INTERVAL = 1000;
|
|
3213
|
+
|
|
3214
|
+
class GatewayHeartbeat {
|
|
3215
|
+
#options;
|
|
3216
|
+
#now;
|
|
3217
|
+
#interval = 0;
|
|
3218
|
+
#acknowledged = true;
|
|
3219
|
+
#sentAt = 0;
|
|
3220
|
+
#latency = -1;
|
|
3221
|
+
#lastMessageAt = 0;
|
|
3222
|
+
#zombieReported = false;
|
|
3223
|
+
#timer;
|
|
3224
|
+
#initialTimer;
|
|
3225
|
+
#ackTimer;
|
|
3226
|
+
#monitorTimer;
|
|
3227
|
+
constructor(options) {
|
|
3228
|
+
if (options.zombieTimeout <= options.ackTimeout)
|
|
3229
|
+
throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
|
|
3230
|
+
this.#options = options;
|
|
3231
|
+
this.#now = options.now ?? (() => Date.now());
|
|
3232
|
+
}
|
|
3233
|
+
get latency() {
|
|
3234
|
+
return this.#latency;
|
|
3235
|
+
}
|
|
3236
|
+
get interval() {
|
|
3237
|
+
return this.#interval;
|
|
3238
|
+
}
|
|
3239
|
+
get acknowledged() {
|
|
3240
|
+
return this.#acknowledged;
|
|
3241
|
+
}
|
|
3242
|
+
get silentFor() {
|
|
3243
|
+
return this.#lastMessageAt === 0 ? 0 : this.#now() - this.#lastMessageAt;
|
|
3244
|
+
}
|
|
3245
|
+
get stalenessDeadline() {
|
|
3246
|
+
return Math.max(this.#options.zombieTimeout, this.#interval + this.#options.ackTimeout);
|
|
3247
|
+
}
|
|
3248
|
+
isHealthy() {
|
|
3249
|
+
if (!this.#acknowledged)
|
|
3250
|
+
return false;
|
|
3251
|
+
if (this.#lastMessageAt === 0)
|
|
3252
|
+
return true;
|
|
3253
|
+
return this.silentFor < this.stalenessDeadline;
|
|
3254
|
+
}
|
|
3255
|
+
watch() {
|
|
3256
|
+
this.#lastMessageAt = this.#now();
|
|
3257
|
+
this.#zombieReported = false;
|
|
3258
|
+
this.#startMonitor();
|
|
3259
|
+
}
|
|
3260
|
+
receivedMessage() {
|
|
3261
|
+
this.#lastMessageAt = this.#now();
|
|
3262
|
+
this.#zombieReported = false;
|
|
3263
|
+
}
|
|
3264
|
+
start(interval) {
|
|
3265
|
+
this.#clearBeatTimers();
|
|
3266
|
+
this.#interval = interval;
|
|
3267
|
+
this.#acknowledged = true;
|
|
3268
|
+
this.#initialTimer = setTimeout(() => this.sendHeartbeat(), Math.random() * interval);
|
|
3269
|
+
this.#timer = setInterval(() => this.sendHeartbeat(), interval);
|
|
3270
|
+
}
|
|
3271
|
+
stop() {
|
|
3272
|
+
this.#clearBeatTimers();
|
|
3273
|
+
if (this.#monitorTimer)
|
|
3274
|
+
clearInterval(this.#monitorTimer);
|
|
3275
|
+
this.#monitorTimer = undefined;
|
|
3276
|
+
}
|
|
3277
|
+
sendHeartbeat() {
|
|
3278
|
+
this.#acknowledged = false;
|
|
3279
|
+
this.#sentAt = this.#now();
|
|
3280
|
+
if (!this.#options.send(this.#options.sequence())) {
|
|
3281
|
+
this.#options.onError(new Error("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
this.#clearAckTimer();
|
|
3285
|
+
this.#ackTimer = setTimeout(() => {
|
|
3286
|
+
if (this.#acknowledged)
|
|
3287
|
+
return;
|
|
3288
|
+
this.#options.onTimeout({
|
|
3289
|
+
type: "ack",
|
|
3290
|
+
elapsedMs: this.#now() - this.#sentAt
|
|
3291
|
+
});
|
|
3292
|
+
}, this.#options.ackTimeout);
|
|
3293
|
+
}
|
|
3294
|
+
acknowledge() {
|
|
3295
|
+
this.#acknowledged = true;
|
|
3296
|
+
this.#latency = this.#now() - this.#sentAt;
|
|
3297
|
+
this.#clearAckTimer();
|
|
3298
|
+
}
|
|
3299
|
+
#startMonitor() {
|
|
3300
|
+
if (this.#monitorTimer)
|
|
3301
|
+
clearInterval(this.#monitorTimer);
|
|
3302
|
+
const interval = Math.max(1, Math.min(MAX_MONITOR_INTERVAL, this.#options.ackTimeout, this.#options.zombieTimeout / 2));
|
|
3303
|
+
this.#monitorTimer = setInterval(() => this.#checkStaleness(), interval);
|
|
3304
|
+
}
|
|
3305
|
+
#checkStaleness() {
|
|
3306
|
+
if (!this.#options.isConnected() || this.#lastMessageAt === 0)
|
|
3307
|
+
return;
|
|
3308
|
+
const silentFor = this.silentFor;
|
|
3309
|
+
const deadline = this.stalenessDeadline;
|
|
3310
|
+
if (silentFor < deadline || this.#zombieReported)
|
|
3311
|
+
return;
|
|
3312
|
+
this.#zombieReported = true;
|
|
3313
|
+
this.#options.onTimeout({ type: "zombie", silentFor, deadline });
|
|
3314
|
+
}
|
|
3315
|
+
#clearAckTimer() {
|
|
3316
|
+
if (this.#ackTimer)
|
|
3317
|
+
clearTimeout(this.#ackTimer);
|
|
3318
|
+
this.#ackTimer = undefined;
|
|
3319
|
+
}
|
|
3320
|
+
#clearBeatTimers() {
|
|
3321
|
+
if (this.#timer)
|
|
3322
|
+
clearInterval(this.#timer);
|
|
3323
|
+
if (this.#initialTimer)
|
|
3324
|
+
clearTimeout(this.#initialTimer);
|
|
3325
|
+
this.#clearAckTimer();
|
|
3326
|
+
this.#timer = undefined;
|
|
3327
|
+
this.#initialTimer = undefined;
|
|
2125
3328
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
// packages/ws/src/close-codes.ts
|
|
3332
|
+
var GatewayCloseCodes = {
|
|
3333
|
+
UnknownError: 4000,
|
|
3334
|
+
UnknownOpcode: 4001,
|
|
3335
|
+
DecodeError: 4002,
|
|
3336
|
+
NotAuthenticated: 4003,
|
|
3337
|
+
AuthenticationFailed: 4004,
|
|
3338
|
+
AlreadyAuthenticated: 4005,
|
|
3339
|
+
InvalidSeq: 4007,
|
|
3340
|
+
RateLimited: 4008,
|
|
3341
|
+
SessionTimedOut: 4009,
|
|
3342
|
+
InvalidShard: 4010,
|
|
3343
|
+
ShardingRequired: 4011,
|
|
3344
|
+
InvalidAPIVersion: 4012,
|
|
3345
|
+
InvalidIntents: 4013,
|
|
3346
|
+
DisallowedIntents: 4014
|
|
3347
|
+
};
|
|
3348
|
+
|
|
3349
|
+
// packages/ws/src/reconnect.ts
|
|
3350
|
+
var FATAL_CLOSE_CODES = [
|
|
3351
|
+
GatewayCloseCodes.AuthenticationFailed,
|
|
3352
|
+
GatewayCloseCodes.InvalidShard,
|
|
3353
|
+
GatewayCloseCodes.ShardingRequired,
|
|
3354
|
+
GatewayCloseCodes.InvalidAPIVersion,
|
|
3355
|
+
GatewayCloseCodes.InvalidIntents,
|
|
3356
|
+
GatewayCloseCodes.DisallowedIntents
|
|
3357
|
+
];
|
|
3358
|
+
var IDENTIFY_CLOSE_CODES = [
|
|
3359
|
+
GatewayCloseCodes.InvalidSeq,
|
|
3360
|
+
GatewayCloseCodes.SessionTimedOut
|
|
3361
|
+
];
|
|
3362
|
+
function classifyCloseCode(code, canResume) {
|
|
3363
|
+
if (FATAL_CLOSE_CODES.includes(code))
|
|
3364
|
+
return "stop";
|
|
3365
|
+
if (IDENTIFY_CLOSE_CODES.includes(code))
|
|
3366
|
+
return "identify";
|
|
3367
|
+
return canResume ? "resume" : "identify";
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
class GatewayReconnect {
|
|
3371
|
+
#options;
|
|
3372
|
+
#random;
|
|
3373
|
+
#attempt = 0;
|
|
3374
|
+
#timer;
|
|
3375
|
+
#promise;
|
|
3376
|
+
#resolve;
|
|
3377
|
+
#reject;
|
|
3378
|
+
constructor(options) {
|
|
3379
|
+
this.#options = options;
|
|
3380
|
+
this.#random = options.random ?? Math.random;
|
|
3381
|
+
}
|
|
3382
|
+
get enabled() {
|
|
3383
|
+
return this.#options.enabled;
|
|
3384
|
+
}
|
|
3385
|
+
get attempts() {
|
|
3386
|
+
return this.#attempt;
|
|
3387
|
+
}
|
|
3388
|
+
get pending() {
|
|
3389
|
+
return this.#timer !== undefined;
|
|
3390
|
+
}
|
|
3391
|
+
get exhausted() {
|
|
3392
|
+
return this.#attempt >= this.#options.maxAttempts;
|
|
3393
|
+
}
|
|
3394
|
+
get connecting() {
|
|
3395
|
+
return this.#promise !== undefined;
|
|
3396
|
+
}
|
|
3397
|
+
get attemptPromise() {
|
|
3398
|
+
return this.#promise;
|
|
3399
|
+
}
|
|
3400
|
+
classifyClose(code, canResume) {
|
|
3401
|
+
return classifyCloseCode(code, canResume);
|
|
3402
|
+
}
|
|
3403
|
+
canReconnect() {
|
|
3404
|
+
return this.#options.enabled && !this.exhausted;
|
|
3405
|
+
}
|
|
3406
|
+
schedule(run) {
|
|
3407
|
+
if (!this.#options.enabled)
|
|
3408
|
+
return { scheduled: false, reason: "disabled" };
|
|
3409
|
+
if (this.#timer)
|
|
3410
|
+
return { scheduled: false, reason: "pending" };
|
|
3411
|
+
if (this.exhausted)
|
|
3412
|
+
return { scheduled: false, reason: "exhausted" };
|
|
3413
|
+
const attempt = this.#attempt;
|
|
3414
|
+
const delay = Math.min(this.#options.maxDelay, this.#options.baseDelay * 2 ** this.#attempt++);
|
|
3415
|
+
const jitter = this.#random() * Math.max(1, delay * 0.25);
|
|
3416
|
+
const delayMs = delay + jitter;
|
|
3417
|
+
this.#timer = setTimeout(() => {
|
|
3418
|
+
this.#timer = undefined;
|
|
3419
|
+
run();
|
|
3420
|
+
}, delayMs);
|
|
3421
|
+
return { scheduled: true, delayMs, attempt };
|
|
3422
|
+
}
|
|
3423
|
+
cancel() {
|
|
3424
|
+
if (this.#timer)
|
|
3425
|
+
clearTimeout(this.#timer);
|
|
3426
|
+
this.#timer = undefined;
|
|
3427
|
+
}
|
|
3428
|
+
reset() {
|
|
3429
|
+
this.#attempt = 0;
|
|
3430
|
+
}
|
|
3431
|
+
attempt(start) {
|
|
3432
|
+
if (this.#promise)
|
|
3433
|
+
return this.#promise;
|
|
3434
|
+
this.#promise = new Promise((resolve, reject) => {
|
|
3435
|
+
this.#resolve = resolve;
|
|
3436
|
+
this.#reject = reject;
|
|
3437
|
+
start();
|
|
2130
3438
|
});
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
3439
|
+
return this.#promise;
|
|
3440
|
+
}
|
|
3441
|
+
settle(error) {
|
|
3442
|
+
const resolve = this.#resolve;
|
|
3443
|
+
const reject = this.#reject;
|
|
3444
|
+
this.#resolve = undefined;
|
|
3445
|
+
this.#reject = undefined;
|
|
3446
|
+
this.#promise = undefined;
|
|
3447
|
+
if (error)
|
|
3448
|
+
reject?.(error);
|
|
3449
|
+
else
|
|
3450
|
+
resolve?.();
|
|
2136
3451
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
// packages/ws/src/decoder.ts
|
|
3455
|
+
import {
|
|
3456
|
+
createInflate,
|
|
3457
|
+
constants as zlibConstants
|
|
3458
|
+
} from "zlib";
|
|
3459
|
+
async function toBytes(data) {
|
|
3460
|
+
if (data instanceof Uint8Array)
|
|
3461
|
+
return data;
|
|
3462
|
+
if (data instanceof ArrayBuffer)
|
|
3463
|
+
return new Uint8Array(data);
|
|
3464
|
+
if (typeof Blob !== "undefined" && data instanceof Blob)
|
|
3465
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
3466
|
+
throw new TypeError("Gateway returned an unsupported compressed frame type.");
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
class PlainTextDecoder {
|
|
3470
|
+
push(data) {
|
|
3471
|
+
return [String(data)];
|
|
2146
3472
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
3473
|
+
reset() {}
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
class ZlibStreamDecoder {
|
|
3477
|
+
#create;
|
|
3478
|
+
#inflate;
|
|
3479
|
+
#chunks = [];
|
|
3480
|
+
#failure;
|
|
3481
|
+
#pendingReject;
|
|
3482
|
+
constructor(create = () => createInflate()) {
|
|
3483
|
+
this.#create = create;
|
|
3484
|
+
}
|
|
3485
|
+
push(data) {
|
|
3486
|
+
if (typeof data === "string")
|
|
3487
|
+
return [data];
|
|
3488
|
+
return this.#inflateFrame(data);
|
|
3489
|
+
}
|
|
3490
|
+
async#inflateFrame(data) {
|
|
3491
|
+
const chunk = await toBytes(data);
|
|
3492
|
+
const inflate = this.#inflate ??= this.#open();
|
|
3493
|
+
if (this.#failure)
|
|
3494
|
+
throw this.#failure;
|
|
3495
|
+
const complete = chunk.length >= 4 && chunk[chunk.length - 4] === 0 && chunk[chunk.length - 3] === 0 && chunk[chunk.length - 2] === 255 && chunk[chunk.length - 1] === 255;
|
|
3496
|
+
await this.#awaiting((settle) => inflate.write(chunk, (error) => error ? settle(error) : settle()));
|
|
3497
|
+
if (!complete)
|
|
3498
|
+
return [];
|
|
3499
|
+
await this.#awaiting((settle) => inflate.flush(zlibConstants.Z_SYNC_FLUSH, () => settle()));
|
|
3500
|
+
const parts = this.#chunks;
|
|
3501
|
+
this.#chunks = [];
|
|
3502
|
+
if (parts.length === 0)
|
|
3503
|
+
return [];
|
|
3504
|
+
const total = parts.reduce((size, part) => size + part.length, 0);
|
|
3505
|
+
const merged = new Uint8Array(total);
|
|
3506
|
+
let offset = 0;
|
|
3507
|
+
for (const part of parts) {
|
|
3508
|
+
merged.set(part, offset);
|
|
3509
|
+
offset += part.length;
|
|
3510
|
+
}
|
|
3511
|
+
return [new TextDecoder().decode(merged)];
|
|
3512
|
+
}
|
|
3513
|
+
reset() {
|
|
3514
|
+
const inflate = this.#inflate;
|
|
3515
|
+
this.#inflate = undefined;
|
|
3516
|
+
this.#chunks = [];
|
|
3517
|
+
this.#failure = undefined;
|
|
3518
|
+
this.#pendingReject = undefined;
|
|
3519
|
+
if (!inflate)
|
|
3520
|
+
return;
|
|
3521
|
+
inflate.removeAllListeners();
|
|
3522
|
+
try {
|
|
3523
|
+
inflate.close();
|
|
3524
|
+
} catch {}
|
|
3525
|
+
}
|
|
3526
|
+
#awaiting(run) {
|
|
3527
|
+
if (this.#failure)
|
|
3528
|
+
return Promise.reject(this.#failure);
|
|
3529
|
+
return new Promise((resolve, reject) => {
|
|
3530
|
+
this.#pendingReject = reject;
|
|
3531
|
+
run((error) => {
|
|
3532
|
+
this.#pendingReject = undefined;
|
|
3533
|
+
if (error)
|
|
3534
|
+
reject(error);
|
|
3535
|
+
else if (this.#failure)
|
|
3536
|
+
reject(this.#failure);
|
|
3537
|
+
else
|
|
3538
|
+
resolve();
|
|
3539
|
+
});
|
|
3540
|
+
});
|
|
2149
3541
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
3542
|
+
#open() {
|
|
3543
|
+
const inflate = this.#create();
|
|
3544
|
+
inflate.on("data", (chunk) => this.#chunks.push(chunk));
|
|
3545
|
+
inflate.on("error", (error) => {
|
|
3546
|
+
this.#failure = error;
|
|
3547
|
+
const reject = this.#pendingReject;
|
|
3548
|
+
this.#pendingReject = undefined;
|
|
3549
|
+
reject?.(error);
|
|
3550
|
+
});
|
|
3551
|
+
return inflate;
|
|
2152
3552
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
3553
|
+
}
|
|
3554
|
+
function createDecoder(compress) {
|
|
3555
|
+
return compress ? new ZlibStreamDecoder : new PlainTextDecoder;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
// packages/ws/src/transport.ts
|
|
3559
|
+
var OPEN = 1;
|
|
3560
|
+
|
|
3561
|
+
class WebSocketTransport {
|
|
3562
|
+
#handlers;
|
|
3563
|
+
#decoder;
|
|
3564
|
+
#createSocket;
|
|
3565
|
+
#compress;
|
|
3566
|
+
#socket;
|
|
3567
|
+
#generation = 0;
|
|
3568
|
+
#queue = Promise.resolve();
|
|
3569
|
+
constructor(options) {
|
|
3570
|
+
this.#handlers = options.handlers;
|
|
3571
|
+
this.#compress = options.compress ?? false;
|
|
3572
|
+
this.#decoder = options.decoder ?? createDecoder(options.compress);
|
|
3573
|
+
this.#createSocket = options.createSocket ?? ((url) => new WebSocket(url));
|
|
3574
|
+
}
|
|
3575
|
+
get connected() {
|
|
3576
|
+
return this.#socket?.readyState === OPEN;
|
|
3577
|
+
}
|
|
3578
|
+
get live() {
|
|
3579
|
+
return this.#socket !== undefined && this.#socket.readyState <= OPEN;
|
|
3580
|
+
}
|
|
3581
|
+
get generation() {
|
|
3582
|
+
return this.#generation;
|
|
3583
|
+
}
|
|
3584
|
+
connect(url) {
|
|
3585
|
+
this.#discard(1000, "Superseded by a new connection");
|
|
3586
|
+
const generation = ++this.#generation;
|
|
3587
|
+
this.#decoder.reset();
|
|
3588
|
+
this.#queue = Promise.resolve();
|
|
3589
|
+
let socket;
|
|
3590
|
+
try {
|
|
3591
|
+
socket = this.#createSocket(url);
|
|
3592
|
+
} catch (error) {
|
|
3593
|
+
return { ok: false, error: this.#asError(error) };
|
|
3594
|
+
}
|
|
3595
|
+
if (this.#compress) {
|
|
3596
|
+
try {
|
|
3597
|
+
socket.binaryType = "arraybuffer";
|
|
3598
|
+
} catch {}
|
|
3599
|
+
}
|
|
3600
|
+
this.#socket = socket;
|
|
3601
|
+
socket.addEventListener("open", () => {
|
|
3602
|
+
if (!this.#owns(generation))
|
|
3603
|
+
return;
|
|
3604
|
+
this.#handlers.onOpen();
|
|
3605
|
+
});
|
|
3606
|
+
socket.addEventListener("message", (event) => {
|
|
3607
|
+
if (!this.#owns(generation))
|
|
3608
|
+
return;
|
|
3609
|
+
let decoded;
|
|
3610
|
+
try {
|
|
3611
|
+
decoded = this.#decoder.push(event.data);
|
|
3612
|
+
} catch (error) {
|
|
3613
|
+
this.#handlers.onError(this.#asError(error));
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3616
|
+
if (!(decoded instanceof Promise)) {
|
|
3617
|
+
for (const frame of decoded)
|
|
3618
|
+
this.#handlers.onFrame(frame);
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
this.#queue = this.#queue.then(async () => {
|
|
3622
|
+
const frames = await decoded;
|
|
3623
|
+
if (!this.#owns(generation))
|
|
3624
|
+
return;
|
|
3625
|
+
for (const frame of frames)
|
|
3626
|
+
this.#handlers.onFrame(frame);
|
|
3627
|
+
}).catch((error) => {
|
|
3628
|
+
if (!this.#owns(generation))
|
|
3629
|
+
return;
|
|
3630
|
+
this.#handlers.onError(this.#asError(error));
|
|
3631
|
+
});
|
|
3632
|
+
});
|
|
3633
|
+
socket.addEventListener("close", (event) => {
|
|
3634
|
+
if (!this.#owns(generation))
|
|
3635
|
+
return;
|
|
3636
|
+
this.#socket = undefined;
|
|
3637
|
+
this.#generation++;
|
|
3638
|
+
this.#decoder.reset();
|
|
3639
|
+
this.#handlers.onClose(event.code, event.reason ?? "");
|
|
3640
|
+
});
|
|
3641
|
+
socket.addEventListener("error", () => {
|
|
3642
|
+
if (!this.#owns(generation))
|
|
3643
|
+
return;
|
|
3644
|
+
this.#handlers.onError(new Error("Gateway WebSocket error"));
|
|
3645
|
+
});
|
|
3646
|
+
return { ok: true };
|
|
2155
3647
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
3648
|
+
send(data) {
|
|
3649
|
+
if (!this.connected)
|
|
3650
|
+
return false;
|
|
3651
|
+
try {
|
|
3652
|
+
this.#socket.send(data);
|
|
3653
|
+
return true;
|
|
3654
|
+
} catch (error) {
|
|
3655
|
+
this.#handlers.onError(this.#asError(error));
|
|
3656
|
+
return false;
|
|
3657
|
+
}
|
|
2159
3658
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
3659
|
+
close(code = 1000, reason = "") {
|
|
3660
|
+
const socket = this.#socket;
|
|
3661
|
+
if (!socket)
|
|
3662
|
+
return;
|
|
3663
|
+
try {
|
|
3664
|
+
socket.close(code, reason);
|
|
3665
|
+
} catch (error) {
|
|
3666
|
+
this.#handlers.onError(this.#asError(error));
|
|
3667
|
+
}
|
|
2162
3668
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
3669
|
+
destroy(code = 1000, reason = "") {
|
|
3670
|
+
this.#discard(code, reason);
|
|
3671
|
+
this.#decoder.reset();
|
|
2165
3672
|
}
|
|
2166
|
-
|
|
2167
|
-
return this
|
|
3673
|
+
#owns(generation) {
|
|
3674
|
+
return generation === this.#generation;
|
|
2168
3675
|
}
|
|
2169
|
-
|
|
2170
|
-
this.#
|
|
2171
|
-
|
|
3676
|
+
#discard(code, reason) {
|
|
3677
|
+
const socket = this.#socket;
|
|
3678
|
+
if (!socket)
|
|
3679
|
+
return;
|
|
3680
|
+
this.#generation++;
|
|
3681
|
+
this.#socket = undefined;
|
|
3682
|
+
try {
|
|
3683
|
+
socket.close(code, reason);
|
|
3684
|
+
} catch (error) {
|
|
3685
|
+
this.#handlers.onError(this.#asError(error));
|
|
3686
|
+
}
|
|
2172
3687
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
super.clear();
|
|
3688
|
+
#asError(error) {
|
|
3689
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
2176
3690
|
}
|
|
2177
3691
|
}
|
|
2178
|
-
function isMessageQuery(value) {
|
|
2179
|
-
return typeof value === "object" && value !== null && !(Symbol.iterator in value);
|
|
2180
|
-
}
|
|
2181
3692
|
|
|
2182
|
-
// packages/ws/src/
|
|
3693
|
+
// packages/ws/src/opcodes.ts
|
|
2183
3694
|
var GatewayOpcodes = {
|
|
2184
3695
|
Dispatch: 0,
|
|
2185
3696
|
Heartbeat: 1,
|
|
@@ -2193,6 +3704,131 @@ var GatewayOpcodes = {
|
|
|
2193
3704
|
Hello: 10,
|
|
2194
3705
|
HeartbeatAck: 11
|
|
2195
3706
|
};
|
|
3707
|
+
|
|
3708
|
+
// packages/ws/src/protocol.ts
|
|
3709
|
+
function classifyFrame(raw) {
|
|
3710
|
+
let payload;
|
|
3711
|
+
try {
|
|
3712
|
+
payload = JSON.parse(raw);
|
|
3713
|
+
} catch (error) {
|
|
3714
|
+
return {
|
|
3715
|
+
sequence: null,
|
|
3716
|
+
action: {
|
|
3717
|
+
type: "invalid",
|
|
3718
|
+
violation: "invalid-json",
|
|
3719
|
+
message: "Gateway returned invalid JSON",
|
|
3720
|
+
cause: error
|
|
3721
|
+
}
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
return classifyPayload(payload);
|
|
3725
|
+
}
|
|
3726
|
+
function classifyPayload(payload) {
|
|
3727
|
+
if (payload === null || typeof payload !== "object" || typeof payload.op !== "number")
|
|
3728
|
+
return {
|
|
3729
|
+
sequence: null,
|
|
3730
|
+
action: {
|
|
3731
|
+
type: "invalid",
|
|
3732
|
+
violation: "invalid-payload",
|
|
3733
|
+
message: "Gateway returned an invalid payload"
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
const frame = payload;
|
|
3737
|
+
const sequence = typeof frame.s === "number" ? frame.s : null;
|
|
3738
|
+
switch (frame.op) {
|
|
3739
|
+
case GatewayOpcodes.Dispatch:
|
|
3740
|
+
return {
|
|
3741
|
+
sequence,
|
|
3742
|
+
action: {
|
|
3743
|
+
type: "dispatch",
|
|
3744
|
+
event: typeof frame.t === "string" ? frame.t : null,
|
|
3745
|
+
data: frame.d
|
|
3746
|
+
}
|
|
3747
|
+
};
|
|
3748
|
+
case GatewayOpcodes.Hello: {
|
|
3749
|
+
const interval = frame.d?.heartbeat_interval;
|
|
3750
|
+
if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0)
|
|
3751
|
+
return {
|
|
3752
|
+
sequence,
|
|
3753
|
+
action: {
|
|
3754
|
+
type: "invalid",
|
|
3755
|
+
violation: "invalid-hello",
|
|
3756
|
+
message: "Gateway HELLO payload contains an invalid heartbeat interval"
|
|
3757
|
+
}
|
|
3758
|
+
};
|
|
3759
|
+
return {
|
|
3760
|
+
sequence,
|
|
3761
|
+
action: { type: "hello", heartbeatInterval: interval }
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3764
|
+
case GatewayOpcodes.Heartbeat:
|
|
3765
|
+
return { sequence, action: { type: "heartbeat" } };
|
|
3766
|
+
case GatewayOpcodes.HeartbeatAck:
|
|
3767
|
+
return {
|
|
3768
|
+
sequence,
|
|
3769
|
+
action: { type: "heartbeat-ack", data: frame.d }
|
|
3770
|
+
};
|
|
3771
|
+
case GatewayOpcodes.Reconnect:
|
|
3772
|
+
return { sequence, action: { type: "reconnect" } };
|
|
3773
|
+
case GatewayOpcodes.InvalidSession:
|
|
3774
|
+
return {
|
|
3775
|
+
sequence,
|
|
3776
|
+
action: {
|
|
3777
|
+
type: "invalid-session",
|
|
3778
|
+
resumable: frame.d === true
|
|
3779
|
+
}
|
|
3780
|
+
};
|
|
3781
|
+
default:
|
|
3782
|
+
return { sequence, action: { type: "unknown", opcode: frame.op } };
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
function identifyPayload(options) {
|
|
3786
|
+
const props = options.properties ?? {};
|
|
3787
|
+
const os = props.os ?? "Android";
|
|
3788
|
+
const browser = props.browser ?? "Discord Android";
|
|
3789
|
+
const device = props.device ?? "Discord Android";
|
|
3790
|
+
return {
|
|
3791
|
+
op: GatewayOpcodes.Identify,
|
|
3792
|
+
d: {
|
|
3793
|
+
token: options.token,
|
|
3794
|
+
intents: options.intents,
|
|
3795
|
+
properties: {
|
|
3796
|
+
os,
|
|
3797
|
+
browser,
|
|
3798
|
+
device,
|
|
3799
|
+
$os: os,
|
|
3800
|
+
$browser: browser,
|
|
3801
|
+
$device: device,
|
|
3802
|
+
...props
|
|
3803
|
+
},
|
|
3804
|
+
presence: {
|
|
3805
|
+
since: options.presence?.since ?? null,
|
|
3806
|
+
activities: options.presence?.activities ?? [],
|
|
3807
|
+
status: options.presence?.status ?? "online",
|
|
3808
|
+
afk: Boolean(options.presence?.afk)
|
|
3809
|
+
},
|
|
3810
|
+
shard: [options.shardId, options.shardCount]
|
|
3811
|
+
},
|
|
3812
|
+
s: null,
|
|
3813
|
+
t: null
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
function resumePayload(token, resume) {
|
|
3817
|
+
return {
|
|
3818
|
+
op: GatewayOpcodes.Resume,
|
|
3819
|
+
d: {
|
|
3820
|
+
token,
|
|
3821
|
+
session_id: resume.sessionId,
|
|
3822
|
+
seq: resume.sequence
|
|
3823
|
+
},
|
|
3824
|
+
s: null,
|
|
3825
|
+
t: null
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
function heartbeatPayload(sequence) {
|
|
3829
|
+
return { op: GatewayOpcodes.Heartbeat, d: sequence, s: null, t: null };
|
|
3830
|
+
}
|
|
3831
|
+
// packages/ws/src/state.ts
|
|
2196
3832
|
class GatewayError extends Error {
|
|
2197
3833
|
code;
|
|
2198
3834
|
constructor(message, code, options) {
|
|
@@ -2201,31 +3837,43 @@ class GatewayError extends Error {
|
|
|
2201
3837
|
this.code = code;
|
|
2202
3838
|
}
|
|
2203
3839
|
}
|
|
3840
|
+
// packages/ws/src/send-budget.ts
|
|
3841
|
+
class SendBudget {
|
|
3842
|
+
#timestamps = [];
|
|
3843
|
+
#limit;
|
|
3844
|
+
#windowMs;
|
|
3845
|
+
constructor(limit = 115, windowMs = 60000) {
|
|
3846
|
+
this.#limit = limit;
|
|
3847
|
+
this.#windowMs = windowMs;
|
|
3848
|
+
}
|
|
3849
|
+
get used() {
|
|
3850
|
+
return this.#timestamps.length;
|
|
3851
|
+
}
|
|
3852
|
+
allows(privileged, now = Date.now()) {
|
|
3853
|
+
while (this.#timestamps.length && now - this.#timestamps[0] >= this.#windowMs)
|
|
3854
|
+
this.#timestamps.shift();
|
|
3855
|
+
return privileged || this.#timestamps.length < this.#limit;
|
|
3856
|
+
}
|
|
3857
|
+
record(now = Date.now()) {
|
|
3858
|
+
this.#timestamps.push(now);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
// packages/ws/src/index.ts
|
|
3863
|
+
var DEFAULT_GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
|
|
2204
3864
|
|
|
2205
3865
|
class Gateway {
|
|
2206
3866
|
state = "CONNECT" /* Connect */;
|
|
2207
3867
|
#options;
|
|
2208
|
-
#
|
|
2209
|
-
#
|
|
2210
|
-
#
|
|
2211
|
-
#
|
|
2212
|
-
#heartbeatTimer;
|
|
2213
|
-
#initialHeartbeat;
|
|
2214
|
-
#heartbeatAckTimer;
|
|
2215
|
-
#heartbeatACK = true;
|
|
2216
|
-
#heartbeatInterval = 0;
|
|
2217
|
-
#heartbeatSentAt = 0;
|
|
2218
|
-
#lastMessageAt = 0;
|
|
2219
|
-
#zombieTimer;
|
|
2220
|
-
#zombieReported = false;
|
|
3868
|
+
#transport;
|
|
3869
|
+
#session = new GatewaySession;
|
|
3870
|
+
#token = 0;
|
|
3871
|
+
#heartbeat;
|
|
2221
3872
|
#closed = false;
|
|
2222
|
-
#
|
|
2223
|
-
#reconnectTimer;
|
|
2224
|
-
#connectPromise;
|
|
2225
|
-
#resolveConnect;
|
|
2226
|
-
#rejectConnect;
|
|
3873
|
+
#reconnect;
|
|
2227
3874
|
#listeners = new Map;
|
|
2228
|
-
#
|
|
3875
|
+
#sendBudget = new SendBudget;
|
|
3876
|
+
ping = -1;
|
|
2229
3877
|
constructor(options) {
|
|
2230
3878
|
if (!options.token?.trim())
|
|
2231
3879
|
throw new TypeError("A Gateway token is required.");
|
|
@@ -2246,39 +3894,78 @@ class Gateway {
|
|
|
2246
3894
|
browser: "Discord Android",
|
|
2247
3895
|
device: "Discord Android"
|
|
2248
3896
|
},
|
|
2249
|
-
presence: {
|
|
2250
|
-
|
|
3897
|
+
presence: {
|
|
3898
|
+
status: "online",
|
|
3899
|
+
activities: [],
|
|
3900
|
+
afk: false,
|
|
3901
|
+
since: null
|
|
3902
|
+
},
|
|
3903
|
+
...options,
|
|
3904
|
+
intents: resolvedIntents
|
|
2251
3905
|
};
|
|
2252
3906
|
if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
|
|
2253
3907
|
throw new RangeError("Gateway shardId must be within the configured shard count.");
|
|
2254
3908
|
if (this.#options.zombieTimeout <= this.#options.heartbeatAckTimeout)
|
|
2255
3909
|
throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
|
|
3910
|
+
this.#transport = new WebSocketTransport({
|
|
3911
|
+
compress: this.#options.compress,
|
|
3912
|
+
createSocket: options.createSocket,
|
|
3913
|
+
handlers: {
|
|
3914
|
+
onOpen: () => this.#onOpen(),
|
|
3915
|
+
onFrame: (raw) => this.#message(raw, this.#token),
|
|
3916
|
+
onClose: (code) => this.#close(code, this.#token),
|
|
3917
|
+
onError: (error) => this.#emitError(error instanceof GatewayError ? error : new GatewayError(error.message, undefined, {
|
|
3918
|
+
cause: error
|
|
3919
|
+
}))
|
|
3920
|
+
}
|
|
3921
|
+
});
|
|
3922
|
+
this.#reconnect = new GatewayReconnect({
|
|
3923
|
+
enabled: this.#options.reconnect,
|
|
3924
|
+
maxAttempts: this.#options.maxReconnectAttempts,
|
|
3925
|
+
baseDelay: this.#options.reconnectBaseDelay,
|
|
3926
|
+
maxDelay: this.#options.reconnectMaxDelay
|
|
3927
|
+
});
|
|
3928
|
+
this.#heartbeat = new GatewayHeartbeat({
|
|
3929
|
+
ackTimeout: this.#options.heartbeatAckTimeout,
|
|
3930
|
+
zombieTimeout: this.#options.zombieTimeout,
|
|
3931
|
+
send: (sequence) => this.#dispatch(heartbeatPayload(sequence), true),
|
|
3932
|
+
sequence: () => this.#session.sequence,
|
|
3933
|
+
isConnected: () => !this.#closed && this.#transport.connected,
|
|
3934
|
+
onTimeout: (timeout) => this.#onHeartbeatTimeout(timeout),
|
|
3935
|
+
onError: (error) => this.#emitError(error)
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3938
|
+
#onHeartbeatTimeout(timeout) {
|
|
3939
|
+
if (timeout.type === "zombie") {
|
|
3940
|
+
this.#emit("zombie", {
|
|
3941
|
+
silentFor: timeout.silentFor,
|
|
3942
|
+
timeout: timeout.deadline
|
|
3943
|
+
});
|
|
3944
|
+
this.#emitError(new GatewayError(`Gateway connection appears stale after ${timeout.silentFor}ms without traffic.`));
|
|
3945
|
+
} else {
|
|
3946
|
+
this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${timeout.elapsedMs}ms.`));
|
|
3947
|
+
}
|
|
3948
|
+
this.#transport.close(1001, timeout.type === "zombie" ? "Zombie Gateway connection" : "Heartbeat timeout");
|
|
2256
3949
|
}
|
|
2257
|
-
connect(url =
|
|
3950
|
+
connect(url = DEFAULT_GATEWAY_URL) {
|
|
2258
3951
|
if (this.state === "CLOSED" /* Closed */)
|
|
2259
3952
|
throw new GatewayError("Gateway has been permanently closed.");
|
|
2260
|
-
|
|
2261
|
-
|
|
3953
|
+
const inFlight = this.#reconnect.attemptPromise;
|
|
3954
|
+
if (inFlight)
|
|
3955
|
+
return inFlight;
|
|
3956
|
+
if (this.#transport.live)
|
|
3957
|
+
return Promise.resolve();
|
|
3958
|
+
this.#reconnect.cancel();
|
|
2262
3959
|
this.#closed = false;
|
|
2263
3960
|
this.#setState("CONNECT" /* Connect */);
|
|
2264
|
-
this.#
|
|
2265
|
-
|
|
2266
|
-
this.#rejectConnect = reject;
|
|
2267
|
-
this.#open(url);
|
|
2268
|
-
});
|
|
2269
|
-
return this.#connectPromise;
|
|
3961
|
+
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
|
|
3962
|
+
return this.#reconnect.attempt(() => this.#open(connectURL));
|
|
2270
3963
|
}
|
|
2271
3964
|
close() {
|
|
2272
3965
|
this.#closed = true;
|
|
2273
3966
|
this.#clearTimers();
|
|
2274
3967
|
this.#settleConnect(new GatewayError("Gateway connection closed before socket open."));
|
|
2275
|
-
|
|
2276
|
-
this.#ws = undefined;
|
|
2277
|
-
try {
|
|
2278
|
-
ws?.close(1000, "Client closed connection");
|
|
2279
|
-
} catch (error) {
|
|
2280
|
-
this.#emitError(error);
|
|
2281
|
-
}
|
|
3968
|
+
this.#transport.destroy(1000, "Client closed connection");
|
|
2282
3969
|
this.#setState("CLOSED" /* Closed */);
|
|
2283
3970
|
}
|
|
2284
3971
|
emit(event, data) {
|
|
@@ -2300,23 +3987,20 @@ class Gateway {
|
|
|
2300
3987
|
send(payload) {
|
|
2301
3988
|
if (!payload || !Number.isInteger(payload.op))
|
|
2302
3989
|
throw new TypeError("Gateway payload must contain an integer opcode.");
|
|
2303
|
-
|
|
3990
|
+
return this.#dispatch(payload, false);
|
|
3991
|
+
}
|
|
3992
|
+
#dispatch(payload, privileged) {
|
|
3993
|
+
if (!this.#transport.connected)
|
|
2304
3994
|
return false;
|
|
2305
3995
|
const now = Date.now();
|
|
2306
|
-
|
|
2307
|
-
this.#sendTimestamps.shift();
|
|
2308
|
-
if (this.#sendTimestamps.length >= 115) {
|
|
3996
|
+
if (!this.#sendBudget.allows(privileged, now)) {
|
|
2309
3997
|
this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
|
|
2310
3998
|
return false;
|
|
2311
3999
|
}
|
|
2312
|
-
|
|
2313
|
-
this.#ws.send(JSON.stringify(payload));
|
|
2314
|
-
this.#sendTimestamps.push(now);
|
|
2315
|
-
return true;
|
|
2316
|
-
} catch (error) {
|
|
2317
|
-
this.#emitError(error);
|
|
4000
|
+
if (!this.#transport.send(JSON.stringify(payload)))
|
|
2318
4001
|
return false;
|
|
2319
|
-
|
|
4002
|
+
this.#sendBudget.record(now);
|
|
4003
|
+
return true;
|
|
2320
4004
|
}
|
|
2321
4005
|
setPresence(data) {
|
|
2322
4006
|
this.#options.presence = { ...this.#options.presence, ...data };
|
|
@@ -2352,289 +4036,142 @@ class Gateway {
|
|
|
2352
4036
|
#open(url) {
|
|
2353
4037
|
if (this.#closed)
|
|
2354
4038
|
return;
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
} catch (error) {
|
|
2359
|
-
const failure = this.#normalizeError(error);
|
|
2360
|
-
this.#emitError(failure);
|
|
2361
|
-
if (this.#options.reconnect) {
|
|
2362
|
-
this.#setState("RECONNECT" /* Reconnect */);
|
|
2363
|
-
this.#scheduleReconnect();
|
|
2364
|
-
} else
|
|
2365
|
-
this.#settleConnect(failure);
|
|
4039
|
+
this.#token = this.#session.beginConnection();
|
|
4040
|
+
const result = this.#transport.connect(url);
|
|
4041
|
+
if (result.ok)
|
|
2366
4042
|
return;
|
|
4043
|
+
const failure = this.#normalizeError(result.error);
|
|
4044
|
+
this.#emitError(failure);
|
|
4045
|
+
if (this.#options.reconnect) {
|
|
4046
|
+
this.#setState("RECONNECT" /* Reconnect */);
|
|
4047
|
+
this.#scheduleReconnect();
|
|
4048
|
+
} else {
|
|
4049
|
+
this.#settleConnect(failure);
|
|
2367
4050
|
}
|
|
2368
|
-
this.#ws = ws;
|
|
2369
|
-
ws.addEventListener("open", () => {
|
|
2370
|
-
this.#lastMessageAt = Date.now();
|
|
2371
|
-
this.#zombieReported = false;
|
|
2372
|
-
this.#startZombieDetection();
|
|
2373
|
-
this.#emit("open", undefined);
|
|
2374
|
-
this.#settleConnect();
|
|
2375
|
-
});
|
|
2376
|
-
ws.addEventListener("message", (event) => {
|
|
2377
|
-
this.#lastMessageAt = Date.now();
|
|
2378
|
-
this.#zombieReported = false;
|
|
2379
|
-
this.#message(ws, String(event.data));
|
|
2380
|
-
});
|
|
2381
|
-
ws.addEventListener("close", (event) => this.#close(ws, event.code));
|
|
2382
|
-
ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
|
|
2383
|
-
}
|
|
2384
|
-
#startZombieDetection() {
|
|
2385
|
-
if (this.#zombieTimer)
|
|
2386
|
-
clearInterval(this.#zombieTimer);
|
|
2387
|
-
const interval = Math.max(1, Math.min(250, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
|
|
2388
|
-
this.#zombieTimer = setInterval(() => {
|
|
2389
|
-
if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
|
|
2390
|
-
return;
|
|
2391
|
-
const silentFor = Date.now() - this.#lastMessageAt;
|
|
2392
|
-
const deadline = Math.max(this.#options.zombieTimeout, this.#heartbeatInterval + this.#options.heartbeatAckTimeout);
|
|
2393
|
-
if (silentFor < deadline || this.#zombieReported)
|
|
2394
|
-
return;
|
|
2395
|
-
this.#zombieReported = true;
|
|
2396
|
-
const error = new GatewayError(`Gateway connection appears stale after ${silentFor}ms without traffic.`);
|
|
2397
|
-
this.#emit("zombie", { silentFor, timeout: deadline });
|
|
2398
|
-
this.#emitError(error);
|
|
2399
|
-
try {
|
|
2400
|
-
this.#ws.close(1001, "Zombie Gateway connection");
|
|
2401
|
-
} catch (closeError) {
|
|
2402
|
-
this.#emitError(closeError);
|
|
2403
|
-
}
|
|
2404
|
-
}, interval);
|
|
2405
4051
|
}
|
|
2406
|
-
#
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
}));
|
|
2414
|
-
ws.close(1002, "Invalid JSON");
|
|
2415
|
-
return;
|
|
2416
|
-
}
|
|
2417
|
-
if (!payload || typeof payload.op !== "number") {
|
|
2418
|
-
this.#emitError(new GatewayError("Gateway returned an invalid payload"));
|
|
2419
|
-
ws.close(1002, "Invalid payload");
|
|
4052
|
+
#onOpen() {
|
|
4053
|
+
this.#heartbeat.watch();
|
|
4054
|
+
this.#emit("open", undefined);
|
|
4055
|
+
this.#settleConnect();
|
|
4056
|
+
}
|
|
4057
|
+
#message(raw, token) {
|
|
4058
|
+
if (!this.#session.owns(token))
|
|
2420
4059
|
return;
|
|
2421
|
-
}
|
|
2422
|
-
if (
|
|
2423
|
-
this.#sequence
|
|
2424
|
-
|
|
2425
|
-
|
|
4060
|
+
const { sequence, action } = classifyFrame(raw);
|
|
4061
|
+
if (sequence !== null)
|
|
4062
|
+
this.#session.recordSequence(sequence, token);
|
|
4063
|
+
this.#apply(action, token);
|
|
4064
|
+
}
|
|
4065
|
+
#apply(action, token) {
|
|
4066
|
+
switch (action.type) {
|
|
4067
|
+
case "dispatch":
|
|
2426
4068
|
this.#setState("DISPATCH" /* Dispatch */);
|
|
2427
|
-
this.#handleDispatch(
|
|
2428
|
-
|
|
2429
|
-
case
|
|
2430
|
-
this.#handleHello(
|
|
2431
|
-
|
|
2432
|
-
case
|
|
4069
|
+
this.#handleDispatch(action.event, action.data, token);
|
|
4070
|
+
return;
|
|
4071
|
+
case "hello":
|
|
4072
|
+
this.#handleHello(action.heartbeatInterval);
|
|
4073
|
+
return;
|
|
4074
|
+
case "heartbeat":
|
|
2433
4075
|
this.#setState("HEARTBEAT" /* Heartbeat */);
|
|
2434
|
-
this.#sendHeartbeat();
|
|
2435
|
-
|
|
2436
|
-
case
|
|
2437
|
-
this.#
|
|
2438
|
-
this.#
|
|
2439
|
-
this.#emit("heartbeatAck",
|
|
2440
|
-
|
|
2441
|
-
case
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
case
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
this.#emit("invalidSession",
|
|
2448
|
-
|
|
2449
|
-
|
|
4076
|
+
this.#heartbeat.sendHeartbeat();
|
|
4077
|
+
return;
|
|
4078
|
+
case "heartbeat-ack":
|
|
4079
|
+
this.#heartbeat.acknowledge();
|
|
4080
|
+
this.ping = this.#heartbeat.latency;
|
|
4081
|
+
this.#emit("heartbeatAck", action.data);
|
|
4082
|
+
return;
|
|
4083
|
+
case "reconnect":
|
|
4084
|
+
this.#transport.close(1001, "Server requested reconnect");
|
|
4085
|
+
return;
|
|
4086
|
+
case "invalid-session": {
|
|
4087
|
+
if (!action.resumable)
|
|
4088
|
+
this.#session.invalidate(token, "invalid-session");
|
|
4089
|
+
this.#emit("invalidSession", action.resumable);
|
|
4090
|
+
this.#transport.close(action.resumable ? GatewayCloseCodes.UnknownError : 1000, action.resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
case "invalid":
|
|
4094
|
+
this.#emitError(new GatewayError(action.message, undefined, action.cause === undefined ? undefined : { cause: action.cause }));
|
|
4095
|
+
this.#transport.close(1002, action.violation === "invalid-json" ? "Invalid JSON" : action.violation === "invalid-hello" ? "Invalid heartbeat interval" : "Invalid payload");
|
|
4096
|
+
return;
|
|
4097
|
+
case "unknown":
|
|
4098
|
+
return;
|
|
2450
4099
|
}
|
|
2451
4100
|
}
|
|
2452
|
-
#handleDispatch(event, data) {
|
|
4101
|
+
#handleDispatch(event, data, token) {
|
|
2453
4102
|
if (event === "READY") {
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
4103
|
+
if (!this.#session.activate(data, token)) {
|
|
4104
|
+
if (this.#session.owns(token))
|
|
4105
|
+
this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
|
|
2457
4106
|
return;
|
|
2458
4107
|
}
|
|
2459
|
-
this.#
|
|
2460
|
-
this.#resumeURL = ready.resume_gateway_url;
|
|
2461
|
-
this.#attempt = 0;
|
|
4108
|
+
this.#reconnect.reset();
|
|
2462
4109
|
this.#setState("READY" /* Ready */);
|
|
2463
4110
|
this.#emit("ready", data);
|
|
4111
|
+
} else if (event === "RESUMED") {
|
|
4112
|
+
this.#reconnect.reset();
|
|
4113
|
+
this.#setState("READY" /* Ready */);
|
|
4114
|
+
this.#emit("resumed", data);
|
|
2464
4115
|
}
|
|
2465
4116
|
this.#emit("RAW", { event: event ?? "UNKNOWN", data });
|
|
2466
4117
|
if (this.#closed)
|
|
2467
4118
|
return;
|
|
2468
4119
|
this.#emit(event ?? "dispatch", data);
|
|
2469
4120
|
}
|
|
2470
|
-
#handleHello(
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
this.#emitError(new GatewayError("Gateway HELLO payload contains an invalid heartbeat interval"));
|
|
2474
|
-
this.#ws?.close(1002, "Invalid heartbeat interval");
|
|
2475
|
-
return;
|
|
2476
|
-
}
|
|
2477
|
-
this.#heartbeatInterval = interval;
|
|
2478
|
-
this.#setState(this.#sessionId && this.#sequence !== null && this.#resumeURL ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
2479
|
-
this.#startHeartbeat(interval);
|
|
4121
|
+
#handleHello(interval) {
|
|
4122
|
+
this.#setState(this.#session.canResume ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
4123
|
+
this.#heartbeat.start(interval);
|
|
2480
4124
|
this.#identifyOrResume();
|
|
2481
4125
|
}
|
|
2482
4126
|
#identifyOrResume() {
|
|
2483
|
-
|
|
4127
|
+
const intent = this.#session.handshake();
|
|
4128
|
+
if (intent.type === "resume") {
|
|
2484
4129
|
this.#setState("RESUME" /* Resume */);
|
|
2485
|
-
this.
|
|
2486
|
-
op: GatewayOpcodes.Resume,
|
|
2487
|
-
d: {
|
|
2488
|
-
token: this.#options.token,
|
|
2489
|
-
session_id: this.#sessionId,
|
|
2490
|
-
seq: this.#sequence
|
|
2491
|
-
},
|
|
2492
|
-
s: null,
|
|
2493
|
-
t: null
|
|
2494
|
-
});
|
|
4130
|
+
this.#dispatch(resumePayload(this.#options.token, intent), true);
|
|
2495
4131
|
return;
|
|
2496
4132
|
}
|
|
2497
4133
|
this.#setState("IDENTIFY" /* Identify */);
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
d: {
|
|
2509
|
-
token: this.#options.token,
|
|
2510
|
-
intents: this.#options.intents,
|
|
2511
|
-
properties: {
|
|
2512
|
-
os,
|
|
2513
|
-
browser,
|
|
2514
|
-
device,
|
|
2515
|
-
$os: os,
|
|
2516
|
-
$browser: browser,
|
|
2517
|
-
$device: device,
|
|
2518
|
-
...props
|
|
2519
|
-
},
|
|
2520
|
-
presence: {
|
|
2521
|
-
since: this.#options.presence?.since ?? null,
|
|
2522
|
-
activities: this.#options.presence?.activities ?? [],
|
|
2523
|
-
status: this.#options.presence?.status ?? "online",
|
|
2524
|
-
afk: Boolean(this.#options.presence?.afk)
|
|
2525
|
-
},
|
|
2526
|
-
shard: [this.#options.shardId, this.#options.shardCount]
|
|
2527
|
-
},
|
|
2528
|
-
s: null,
|
|
2529
|
-
t: null
|
|
2530
|
-
});
|
|
2531
|
-
}
|
|
2532
|
-
#startHeartbeat(interval) {
|
|
2533
|
-
this.#clearHeartbeatTimers();
|
|
2534
|
-
this.#heartbeatACK = true;
|
|
2535
|
-
this.#initialHeartbeat = setTimeout(() => this.#sendHeartbeat(), Math.random() * interval);
|
|
2536
|
-
this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), interval);
|
|
2537
|
-
}
|
|
2538
|
-
#sendHeartbeat() {
|
|
2539
|
-
this.#heartbeatACK = false;
|
|
2540
|
-
this.#heartbeatSentAt = Date.now();
|
|
2541
|
-
if (!this.send({
|
|
2542
|
-
op: GatewayOpcodes.Heartbeat,
|
|
2543
|
-
d: this.#sequence,
|
|
2544
|
-
s: null,
|
|
2545
|
-
t: null
|
|
2546
|
-
})) {
|
|
2547
|
-
this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
2548
|
-
return;
|
|
2549
|
-
}
|
|
2550
|
-
this.#clearHeartbeatAckTimer();
|
|
2551
|
-
this.#heartbeatAckTimer = setTimeout(() => {
|
|
2552
|
-
if (!this.#heartbeatACK) {
|
|
2553
|
-
const elapsed = Date.now() - this.#heartbeatSentAt;
|
|
2554
|
-
this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${elapsed}ms.`));
|
|
2555
|
-
try {
|
|
2556
|
-
this.#ws?.close(1001, "Heartbeat timeout");
|
|
2557
|
-
} catch (error) {
|
|
2558
|
-
this.#emitError(error);
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
}, this.#options.heartbeatAckTimeout);
|
|
2562
|
-
}
|
|
2563
|
-
#close(ws, code) {
|
|
2564
|
-
if (this.#ws !== ws)
|
|
2565
|
-
return;
|
|
2566
|
-
this.#ws = undefined;
|
|
4134
|
+
this.#dispatch(identifyPayload({
|
|
4135
|
+
token: this.#options.token,
|
|
4136
|
+
intents: this.#options.intents,
|
|
4137
|
+
shardId: this.#options.shardId,
|
|
4138
|
+
shardCount: this.#options.shardCount,
|
|
4139
|
+
properties: this.#options.properties,
|
|
4140
|
+
presence: this.#options.presence
|
|
4141
|
+
}), true);
|
|
4142
|
+
}
|
|
4143
|
+
#close(code, token) {
|
|
2567
4144
|
this.#clearTimers();
|
|
2568
|
-
const action = this.#
|
|
4145
|
+
const action = this.#reconnect.classifyClose(code, this.#session.canResume);
|
|
2569
4146
|
this.#emit("close", { code, action });
|
|
2570
|
-
if (this.#closed || !this.#
|
|
4147
|
+
if (this.#closed || !this.#reconnect.enabled || action === "stop") {
|
|
4148
|
+
if (action === "stop")
|
|
4149
|
+
this.#closed = true;
|
|
2571
4150
|
this.#setState(this.#closed ? "CLOSED" /* Closed */ : "CONNECT" /* Connect */);
|
|
2572
|
-
if (this.#
|
|
2573
|
-
this.#
|
|
4151
|
+
if (this.#reconnect.connecting)
|
|
4152
|
+
this.#reconnect.settle(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
|
|
2574
4153
|
return;
|
|
2575
4154
|
}
|
|
2576
|
-
if (action === "identify")
|
|
2577
|
-
this.#
|
|
2578
|
-
this.#sessionId = undefined;
|
|
2579
|
-
}
|
|
4155
|
+
if (action === "identify")
|
|
4156
|
+
this.#session.invalidate(token, "session-timeout");
|
|
2580
4157
|
this.#setState("RECONNECT" /* Reconnect */);
|
|
2581
4158
|
this.#scheduleReconnect();
|
|
2582
4159
|
}
|
|
2583
|
-
#closeAction(code) {
|
|
2584
|
-
if ([4004, 4010, 4011, 4012, 4013, 4014].includes(code))
|
|
2585
|
-
return "stop";
|
|
2586
|
-
if ([4007, 4009].includes(code))
|
|
2587
|
-
return "identify";
|
|
2588
|
-
return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
|
|
2589
|
-
}
|
|
2590
4160
|
#scheduleReconnect() {
|
|
2591
|
-
if (this.#closed
|
|
4161
|
+
if (this.#closed)
|
|
2592
4162
|
return;
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
this.#setState("CONNECT" /* Connect */);
|
|
4163
|
+
const result = this.#reconnect.schedule(() => this.#open(this.#session.connectURL(DEFAULT_GATEWAY_URL)));
|
|
4164
|
+
if (result.scheduled || result.reason !== "exhausted")
|
|
2596
4165
|
return;
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
const jitter = Math.random() * Math.min(250, Math.max(1, delay * 0.25));
|
|
2600
|
-
this.#reconnectTimer = setTimeout(() => {
|
|
2601
|
-
this.#reconnectTimer = undefined;
|
|
2602
|
-
this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
|
|
2603
|
-
}, delay + jitter);
|
|
4166
|
+
this.#reconnect.settle(new GatewayError("Gateway reconnect attempts exhausted."));
|
|
4167
|
+
this.#setState("CONNECT" /* Connect */);
|
|
2604
4168
|
}
|
|
2605
4169
|
#settleConnect(error) {
|
|
2606
|
-
|
|
2607
|
-
const reject = this.#rejectConnect;
|
|
2608
|
-
this.#resolveConnect = undefined;
|
|
2609
|
-
this.#rejectConnect = undefined;
|
|
2610
|
-
this.#connectPromise = undefined;
|
|
2611
|
-
if (error)
|
|
2612
|
-
reject?.(error);
|
|
2613
|
-
else
|
|
2614
|
-
resolve?.();
|
|
2615
|
-
}
|
|
2616
|
-
#clearHeartbeatAckTimer() {
|
|
2617
|
-
if (this.#heartbeatAckTimer)
|
|
2618
|
-
clearTimeout(this.#heartbeatAckTimer);
|
|
2619
|
-
this.#heartbeatAckTimer = undefined;
|
|
2620
|
-
}
|
|
2621
|
-
#clearHeartbeatTimers() {
|
|
2622
|
-
if (this.#heartbeatTimer)
|
|
2623
|
-
clearInterval(this.#heartbeatTimer);
|
|
2624
|
-
if (this.#initialHeartbeat)
|
|
2625
|
-
clearTimeout(this.#initialHeartbeat);
|
|
2626
|
-
this.#clearHeartbeatAckTimer();
|
|
2627
|
-
this.#heartbeatTimer = undefined;
|
|
2628
|
-
this.#initialHeartbeat = undefined;
|
|
4170
|
+
this.#reconnect.settle(error);
|
|
2629
4171
|
}
|
|
2630
4172
|
#clearTimers() {
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
if (this.#zombieTimer)
|
|
2634
|
-
clearInterval(this.#zombieTimer);
|
|
2635
|
-
this.#reconnectTimer = undefined;
|
|
2636
|
-
this.#zombieTimer = undefined;
|
|
2637
|
-
this.#clearHeartbeatTimers();
|
|
4173
|
+
this.#reconnect.cancel();
|
|
4174
|
+
this.#heartbeat.stop();
|
|
2638
4175
|
}
|
|
2639
4176
|
#normalizeError(error) {
|
|
2640
4177
|
return error instanceof GatewayError ? error : new GatewayError("Gateway connection failed.", undefined, {
|
|
@@ -2669,7 +4206,7 @@ class Gateway {
|
|
|
2669
4206
|
}
|
|
2670
4207
|
|
|
2671
4208
|
// packages/core/src/index.ts
|
|
2672
|
-
class
|
|
4209
|
+
class EventEmitter2 {
|
|
2673
4210
|
#listeners = new Map;
|
|
2674
4211
|
on(event, listener) {
|
|
2675
4212
|
if (typeof listener !== "function")
|
|
@@ -2691,6 +4228,9 @@ class EventEmitter {
|
|
|
2691
4228
|
this.#listeners.get(event)?.delete(listener);
|
|
2692
4229
|
return this;
|
|
2693
4230
|
}
|
|
4231
|
+
removeListener(event, listener) {
|
|
4232
|
+
return this.off(event, listener);
|
|
4233
|
+
}
|
|
2694
4234
|
removeAllListeners(event) {
|
|
2695
4235
|
if (event === undefined)
|
|
2696
4236
|
this.#listeners.clear();
|
|
@@ -2727,12 +4267,13 @@ class EventEmitter {
|
|
|
2727
4267
|
}
|
|
2728
4268
|
}
|
|
2729
4269
|
|
|
2730
|
-
class Client extends
|
|
4270
|
+
class Client extends EventEmitter2 {
|
|
2731
4271
|
options;
|
|
2732
4272
|
rest;
|
|
2733
4273
|
users;
|
|
2734
4274
|
guilds;
|
|
2735
4275
|
channels;
|
|
4276
|
+
stageInstances;
|
|
2736
4277
|
application;
|
|
2737
4278
|
get ws() {
|
|
2738
4279
|
return this.#gateway;
|
|
@@ -2743,14 +4284,26 @@ class Client extends EventEmitter {
|
|
|
2743
4284
|
user;
|
|
2744
4285
|
readyAt;
|
|
2745
4286
|
state = "idle";
|
|
4287
|
+
get token() {
|
|
4288
|
+
return this.state === "destroyed" ? null : this.options.token ?? null;
|
|
4289
|
+
}
|
|
2746
4290
|
get uptime() {
|
|
2747
4291
|
return this.readyAt ? Date.now() - this.readyAt.getTime() : null;
|
|
2748
4292
|
}
|
|
4293
|
+
get ping() {
|
|
4294
|
+
return this.#gateway.ping;
|
|
4295
|
+
}
|
|
2749
4296
|
isReady() {
|
|
2750
4297
|
return this.state === "ready";
|
|
2751
4298
|
}
|
|
2752
4299
|
setPresence(data) {
|
|
2753
|
-
return this.#gateway.setPresence
|
|
4300
|
+
return this.#gateway.setPresence(data);
|
|
4301
|
+
}
|
|
4302
|
+
setVoiceState(data) {
|
|
4303
|
+
return this.#gateway.setVoiceState(data);
|
|
4304
|
+
}
|
|
4305
|
+
requestGuildMembers(data) {
|
|
4306
|
+
return this.#gateway.requestGuildMembers(data);
|
|
2754
4307
|
}
|
|
2755
4308
|
#gateway;
|
|
2756
4309
|
#resourceContext;
|
|
@@ -2763,14 +4316,15 @@ class Client extends EventEmitter {
|
|
|
2763
4316
|
this.users = new UserManager(this.rest);
|
|
2764
4317
|
this.guilds = new GuildManager(this.rest);
|
|
2765
4318
|
this.channels = new ChannelManager(this.rest);
|
|
4319
|
+
this.stageInstances = new StageInstanceManager(this.rest);
|
|
2766
4320
|
const placeholderAppCommands = new ApplicationCommandManager(this.rest, "0");
|
|
2767
4321
|
this.application = { commands: placeholderAppCommands };
|
|
2768
4322
|
this.#resourceContext = {
|
|
2769
|
-
sendMessage: (channelId,
|
|
2770
|
-
editMessage: (channelId, messageId,
|
|
4323
|
+
sendMessage: (channelId, options) => this.channels.send(channelId, options),
|
|
4324
|
+
editMessage: (channelId, messageId, options) => this.channels.editMessage(channelId, messageId, options),
|
|
2771
4325
|
deleteMessage: (channelId, messageId) => this.channels.deleteMessage(channelId, messageId),
|
|
2772
4326
|
crosspostMessage: (channelId, messageId) => this.channels.crosspostMessage(channelId, messageId),
|
|
2773
|
-
editChannel: (channelId,
|
|
4327
|
+
editChannel: (channelId, options) => this.channels.edit(channelId, options),
|
|
2774
4328
|
deleteChannel: (channelId) => this.channels.deleteChannel(channelId),
|
|
2775
4329
|
addReaction: (channelId, messageId, emoji) => this.channels.addReaction(channelId, messageId, emoji),
|
|
2776
4330
|
removeOwnReaction: (channelId, messageId, emoji) => this.channels.removeOwnReaction(channelId, messageId, emoji),
|
|
@@ -2794,6 +4348,13 @@ class Client extends EventEmitter {
|
|
|
2794
4348
|
this.application.commands = new ApplicationCommandManager(this.rest, appId);
|
|
2795
4349
|
this.emit("ready" /* Ready */, this.user);
|
|
2796
4350
|
});
|
|
4351
|
+
this.#gateway.on("RESUMED", () => {
|
|
4352
|
+
this.state = "ready";
|
|
4353
|
+
this.emit("resumed" /* Resumed */);
|
|
4354
|
+
});
|
|
4355
|
+
this.#gateway.on("invalidSession", (isRecoverable) => {
|
|
4356
|
+
this.emit("invalidSession" /* InvalidSession */, isRecoverable);
|
|
4357
|
+
});
|
|
2797
4358
|
this.#gateway.on("open", () => this.emit("open" /* Open */));
|
|
2798
4359
|
this.#gateway.on("close", (data) => {
|
|
2799
4360
|
if (this.state !== "destroyed")
|
|
@@ -2826,6 +4387,7 @@ class Client extends EventEmitter {
|
|
|
2826
4387
|
this.emit("guildUnavailable" /* GuildUnavailable */, payload);
|
|
2827
4388
|
return;
|
|
2828
4389
|
}
|
|
4390
|
+
const wasCached = this.guilds.has(payload.id);
|
|
2829
4391
|
const guild = new Guild(payload);
|
|
2830
4392
|
this.guilds.set(guild.id, guild);
|
|
2831
4393
|
for (const member of payload.members ?? [])
|
|
@@ -2834,8 +4396,8 @@ class Client extends EventEmitter {
|
|
|
2834
4396
|
...payload.channels ?? [],
|
|
2835
4397
|
...payload.threads ?? []
|
|
2836
4398
|
])
|
|
2837
|
-
this.channels.set(channelData.id,
|
|
2838
|
-
this.emit("guildCreate" /* GuildCreate */, payload);
|
|
4399
|
+
this.channels.set(channelData.id, createChannel(channelData, this.#resourceContext));
|
|
4400
|
+
this.emit(wasCached ? "guildAvailable" /* GuildAvailable */ : "guildCreate" /* GuildCreate */, payload);
|
|
2839
4401
|
});
|
|
2840
4402
|
this.#gateway.on("GUILD_UPDATE", (data) => {
|
|
2841
4403
|
const guild = new Guild(data);
|
|
@@ -2881,12 +4443,12 @@ class Client extends EventEmitter {
|
|
|
2881
4443
|
this.#gateway.on("AUTO_MODERATION_RULE_DELETE", (data) => this.emit("autoModerationRuleDelete" /* AutoModerationRuleDelete */, data));
|
|
2882
4444
|
this.#gateway.on("AUTO_MODERATION_ACTION_EXECUTION", (data) => this.emit("autoModerationActionExecution" /* AutoModerationActionExecution */, data));
|
|
2883
4445
|
this.#gateway.on("CHANNEL_CREATE", (data) => {
|
|
2884
|
-
const channel =
|
|
4446
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
2885
4447
|
this.channels.update(channel);
|
|
2886
4448
|
this.emit("channelCreate" /* ChannelCreate */, channel);
|
|
2887
4449
|
});
|
|
2888
4450
|
this.#gateway.on("CHANNEL_UPDATE", (data) => {
|
|
2889
|
-
const channel =
|
|
4451
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
2890
4452
|
this.channels.update(channel);
|
|
2891
4453
|
this.emit("channelUpdate" /* ChannelUpdate */, channel);
|
|
2892
4454
|
});
|
|
@@ -2897,12 +4459,12 @@ class Client extends EventEmitter {
|
|
|
2897
4459
|
});
|
|
2898
4460
|
this.#gateway.on("CHANNEL_PINS_UPDATE", (data) => this.emit("channelPinsUpdate" /* ChannelPinsUpdate */, data));
|
|
2899
4461
|
this.#gateway.on("THREAD_CREATE", (data) => {
|
|
2900
|
-
const channel =
|
|
4462
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
2901
4463
|
this.channels.update(channel);
|
|
2902
4464
|
this.emit("threadCreate" /* ThreadCreate */, channel);
|
|
2903
4465
|
});
|
|
2904
4466
|
this.#gateway.on("THREAD_UPDATE", (data) => {
|
|
2905
|
-
const channel =
|
|
4467
|
+
const channel = createChannel(data, this.#resourceContext);
|
|
2906
4468
|
this.channels.update(channel);
|
|
2907
4469
|
this.emit("threadUpdate" /* ThreadUpdate */, channel);
|
|
2908
4470
|
});
|
|
@@ -2964,13 +4526,57 @@ class Client extends EventEmitter {
|
|
|
2964
4526
|
return Promise.reject(new Error("Client is unauthenticated."));
|
|
2965
4527
|
return this.rest.post(`/webhooks/${this.user.id}/${token}`, data);
|
|
2966
4528
|
}
|
|
4529
|
+
fetchWebhook(id, token) {
|
|
4530
|
+
return this.rest.get(token ? `/webhooks/${id}/${token}` : `/webhooks/${id}`);
|
|
4531
|
+
}
|
|
4532
|
+
fetchGuildPreview(guildId) {
|
|
4533
|
+
return this.rest.get(`/guilds/${guildId}/preview`);
|
|
4534
|
+
}
|
|
4535
|
+
fetchVoiceRegions() {
|
|
4536
|
+
return this.rest.get("/voice/regions");
|
|
4537
|
+
}
|
|
4538
|
+
fetchInvite(code, options) {
|
|
4539
|
+
const query = new URLSearchParams;
|
|
4540
|
+
if (options?.withCounts)
|
|
4541
|
+
query.set("with_counts", "true");
|
|
4542
|
+
if (options?.withExpiration)
|
|
4543
|
+
query.set("with_expiration", "true");
|
|
4544
|
+
if (options?.guildScheduledEventId)
|
|
4545
|
+
query.set("guild_scheduled_event_id", options.guildScheduledEventId);
|
|
4546
|
+
const qs = query.toString();
|
|
4547
|
+
return this.rest.get(`/invites/${encodeURIComponent(code)}${qs ? `?${qs}` : ""}`);
|
|
4548
|
+
}
|
|
4549
|
+
fetchSticker(id) {
|
|
4550
|
+
return this.rest.get(`/stickers/${id}`);
|
|
4551
|
+
}
|
|
4552
|
+
fetchPremiumStickerPacks() {
|
|
4553
|
+
return this.rest.get(`/sticker-packs`);
|
|
4554
|
+
}
|
|
4555
|
+
fetchGuildTemplate(code) {
|
|
4556
|
+
return this.rest.get(`/guilds/templates/${encodeURIComponent(code)}`);
|
|
4557
|
+
}
|
|
4558
|
+
generateInvite(options) {
|
|
4559
|
+
if (!this.user)
|
|
4560
|
+
throw new Error("Client must be ready to generate invite.");
|
|
4561
|
+
const params = new URLSearchParams({
|
|
4562
|
+
client_id: this.user.id,
|
|
4563
|
+
scope: (options?.scopes ?? ["bot"]).join(" ")
|
|
4564
|
+
});
|
|
4565
|
+
if (options?.permissions) {
|
|
4566
|
+
params.set("permissions", String(options.permissions));
|
|
4567
|
+
}
|
|
4568
|
+
return `https://discord.com/api/oauth2/authorize?${params.toString()}`;
|
|
4569
|
+
}
|
|
2967
4570
|
}
|
|
2968
4571
|
export {
|
|
2969
4572
|
Client,
|
|
2970
4573
|
ClientEvent,
|
|
4574
|
+
Collector,
|
|
4575
|
+
ClientEvent as Events,
|
|
2971
4576
|
GatewayIntentBits,
|
|
2972
4577
|
IntentBits,
|
|
2973
4578
|
Intents,
|
|
4579
|
+
IntentsBitField,
|
|
2974
4580
|
Permission,
|
|
2975
4581
|
PermissionFlagsBits,
|
|
2976
4582
|
PermissionOverwriteType,
|
|
@@ -2980,4 +4586,4 @@ export {
|
|
|
2980
4586
|
resolveGatewayIntents
|
|
2981
4587
|
};
|
|
2982
4588
|
|
|
2983
|
-
//# debugId=
|
|
4589
|
+
//# debugId=B9E28396E97D857564756E2164756E21
|