lunibee 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +229 -0
- package/dist/builders/attachment.d.ts +20 -0
- package/dist/builders/commands.d.ts +118 -0
- package/dist/builders/components.d.ts +151 -0
- package/dist/builders/embed.d.ts +59 -0
- package/dist/builders/index.d.ts +5 -0
- package/dist/builders/index.js +828 -0
- package/dist/builders/index.js.map +13 -0
- package/dist/collection/cache.d.ts +37 -0
- package/dist/collection/index.d.ts +47 -0
- package/dist/collection/index.js +214 -0
- package/dist/collection/index.js.map +11 -0
- package/dist/core/client-state.d.ts +10 -0
- package/dist/core/collector.d.ts +27 -0
- package/dist/core/events.d.ts +5 -0
- package/dist/core/index.d.ts +111 -0
- package/dist/core/index.js +3188 -0
- package/dist/core/index.js.map +29 -0
- package/dist/core/permissions.d.ts +293 -0
- package/dist/formatters/index.d.ts +10 -0
- package/dist/formatters/index.js +43 -0
- package/dist/formatters/index.js.map +10 -0
- package/dist/handlers/index.d.ts +16 -0
- package/dist/handlers/index.js +37 -0
- package/dist/handlers/index.js.map +10 -0
- package/{src/index.ts → dist/index.d.ts} +2 -29
- package/dist/index.js +4182 -0
- package/dist/index.js.map +41 -0
- package/dist/managers/application.d.ts +28 -0
- package/dist/managers/base.d.ts +28 -0
- package/dist/managers/index.d.ts +96 -0
- package/dist/managers/index.js +947 -0
- package/dist/managers/index.js.map +22 -0
- package/dist/managers/member.d.ts +35 -0
- package/dist/managers/message.d.ts +25 -0
- package/dist/managers/role.d.ts +34 -0
- package/dist/managers/thread.d.ts +14 -0
- package/dist/rest/index.d.ts +49 -0
- package/dist/rest/index.js +344 -0
- package/dist/rest/index.js.map +12 -0
- package/dist/rest/routes.d.ts +44 -0
- package/dist/rest/types.d.ts +26 -0
- package/dist/rest/webhook.d.ts +40 -0
- package/dist/sharding/bus.d.ts +22 -0
- package/dist/sharding/index.d.ts +34 -0
- package/dist/sharding/index.js +732 -0
- package/dist/sharding/index.js.map +13 -0
- package/dist/structures/base.d.ts +75 -0
- package/dist/structures/index.d.ts +39 -0
- package/dist/structures/index.js +571 -0
- package/dist/structures/index.js.map +14 -0
- package/dist/structures/interactions.d.ts +136 -0
- package/dist/structures/permissions.d.ts +73 -0
- package/dist/structures/resources.d.ts +64 -0
- package/dist/types/index.d.ts +535 -0
- package/dist/types/index.js +132 -0
- package/dist/types/index.js.map +10 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/index.js.map +10 -0
- package/dist/voice/index.d.ts +97 -0
- package/dist/voice/index.js +161 -0
- package/dist/voice/index.js.map +10 -0
- package/dist/ws/index.d.ts +76 -0
- package/dist/ws/index.js +582 -0
- package/dist/ws/index.js.map +11 -0
- package/package.json +113 -28
package/dist/ws/index.js
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/types/src/index.ts
|
|
3
|
+
var GatewayIntentBits = {
|
|
4
|
+
Guilds: 1 << 0,
|
|
5
|
+
GuildMembers: 1 << 1,
|
|
6
|
+
GuildModeration: 1 << 2,
|
|
7
|
+
GuildExpressions: 1 << 3,
|
|
8
|
+
GuildIntegrations: 1 << 4,
|
|
9
|
+
GuildWebhooks: 1 << 5,
|
|
10
|
+
GuildInvites: 1 << 6,
|
|
11
|
+
GuildVoiceStates: 1 << 7,
|
|
12
|
+
GuildPresences: 1 << 8,
|
|
13
|
+
GuildMessages: 1 << 9,
|
|
14
|
+
GuildMessageReactions: 1 << 10,
|
|
15
|
+
GuildMessageTyping: 1 << 11,
|
|
16
|
+
DirectMessages: 1 << 12,
|
|
17
|
+
DirectMessageReactions: 1 << 13,
|
|
18
|
+
DirectMessageTyping: 1 << 14,
|
|
19
|
+
MessageContent: 1 << 15,
|
|
20
|
+
GuildScheduledEvents: 1 << 16,
|
|
21
|
+
AutoModerationConfiguration: 1 << 20,
|
|
22
|
+
AutoModerationExecution: 1 << 21,
|
|
23
|
+
GuildMessagePolls: 1 << 24,
|
|
24
|
+
DirectMessagePolls: 1 << 25
|
|
25
|
+
};
|
|
26
|
+
var IntentBits = {
|
|
27
|
+
guild: 1 << 0,
|
|
28
|
+
guilds: 1 << 0,
|
|
29
|
+
guildMembers: 1 << 1,
|
|
30
|
+
guildModeration: 1 << 2,
|
|
31
|
+
guildBans: 1 << 2,
|
|
32
|
+
guildExpressions: 1 << 3,
|
|
33
|
+
guildEmojis: 1 << 3,
|
|
34
|
+
guildEmojisAndStickers: 1 << 3,
|
|
35
|
+
guildIntegrations: 1 << 4,
|
|
36
|
+
guildWebhooks: 1 << 5,
|
|
37
|
+
guildInvites: 1 << 6,
|
|
38
|
+
guildVoiceStates: 1 << 7,
|
|
39
|
+
guildPresences: 1 << 8,
|
|
40
|
+
guildMessages: 1 << 9,
|
|
41
|
+
guildMessage: 1 << 9,
|
|
42
|
+
guildMessageReactions: 1 << 10,
|
|
43
|
+
guildMessageTyping: 1 << 11,
|
|
44
|
+
directMessages: 1 << 12,
|
|
45
|
+
directMessageReactions: 1 << 13,
|
|
46
|
+
directMessageTyping: 1 << 14,
|
|
47
|
+
messageContent: 1 << 15,
|
|
48
|
+
guildScheduledEvents: 1 << 16,
|
|
49
|
+
autoModerationConfiguration: 1 << 20,
|
|
50
|
+
autoModerationExecution: 1 << 21,
|
|
51
|
+
guildMessagePolls: 1 << 24,
|
|
52
|
+
directMessagePolls: 1 << 25
|
|
53
|
+
};
|
|
54
|
+
function resolveGatewayIntents(intents) {
|
|
55
|
+
if (typeof intents === "number")
|
|
56
|
+
return intents;
|
|
57
|
+
if (Array.isArray(intents)) {
|
|
58
|
+
return intents.reduce((acc, intent) => {
|
|
59
|
+
return acc | resolveGatewayIntents(intent);
|
|
60
|
+
}, 0);
|
|
61
|
+
}
|
|
62
|
+
if (typeof intents === "string") {
|
|
63
|
+
if (intents in GatewayIntentBits)
|
|
64
|
+
return GatewayIntentBits[intents];
|
|
65
|
+
if (intents in IntentBits)
|
|
66
|
+
return IntentBits[intents];
|
|
67
|
+
const lower = intents.charAt(0).toLowerCase() + intents.slice(1);
|
|
68
|
+
if (lower in IntentBits)
|
|
69
|
+
return IntentBits[lower];
|
|
70
|
+
const num = Number(intents);
|
|
71
|
+
if (!Number.isNaN(num))
|
|
72
|
+
return num;
|
|
73
|
+
}
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// packages/ws/src/index.ts
|
|
78
|
+
var GatewayOpcodes = {
|
|
79
|
+
Dispatch: 0,
|
|
80
|
+
Heartbeat: 1,
|
|
81
|
+
Identify: 2,
|
|
82
|
+
PresenceUpdate: 3,
|
|
83
|
+
VoiceStateUpdate: 4,
|
|
84
|
+
Resume: 6,
|
|
85
|
+
Reconnect: 7,
|
|
86
|
+
RequestGuildMembers: 8,
|
|
87
|
+
InvalidSession: 9,
|
|
88
|
+
Hello: 10,
|
|
89
|
+
HeartbeatAck: 11
|
|
90
|
+
};
|
|
91
|
+
var GatewayState;
|
|
92
|
+
((GatewayState2) => {
|
|
93
|
+
GatewayState2["Connect"] = "CONNECT";
|
|
94
|
+
GatewayState2["Hello"] = "HELLO";
|
|
95
|
+
GatewayState2["Identify"] = "IDENTIFY";
|
|
96
|
+
GatewayState2["Resume"] = "RESUME";
|
|
97
|
+
GatewayState2["Ready"] = "READY";
|
|
98
|
+
GatewayState2["Dispatch"] = "DISPATCH";
|
|
99
|
+
GatewayState2["Heartbeat"] = "HEARTBEAT";
|
|
100
|
+
GatewayState2["Reconnect"] = "RECONNECT";
|
|
101
|
+
GatewayState2["Closed"] = "CLOSED";
|
|
102
|
+
})(GatewayState ||= {});
|
|
103
|
+
|
|
104
|
+
class GatewayError extends Error {
|
|
105
|
+
code;
|
|
106
|
+
constructor(message, code, options) {
|
|
107
|
+
super(message, options);
|
|
108
|
+
this.name = "GatewayError";
|
|
109
|
+
this.code = code;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class Gateway {
|
|
114
|
+
state = "CONNECT" /* Connect */;
|
|
115
|
+
#options;
|
|
116
|
+
#ws;
|
|
117
|
+
#sequence = null;
|
|
118
|
+
#sessionId;
|
|
119
|
+
#resumeURL;
|
|
120
|
+
#heartbeatTimer;
|
|
121
|
+
#initialHeartbeat;
|
|
122
|
+
#heartbeatAckTimer;
|
|
123
|
+
#heartbeatACK = true;
|
|
124
|
+
#heartbeatInterval = 0;
|
|
125
|
+
#heartbeatSentAt = 0;
|
|
126
|
+
#lastMessageAt = 0;
|
|
127
|
+
#zombieTimer;
|
|
128
|
+
#zombieReported = false;
|
|
129
|
+
#closed = false;
|
|
130
|
+
#attempt = 0;
|
|
131
|
+
#reconnectTimer;
|
|
132
|
+
#connectPromise;
|
|
133
|
+
#resolveConnect;
|
|
134
|
+
#rejectConnect;
|
|
135
|
+
#listeners = new Map;
|
|
136
|
+
#sendTimestamps = [];
|
|
137
|
+
constructor(options) {
|
|
138
|
+
if (!options.token?.trim())
|
|
139
|
+
throw new TypeError("A Gateway token is required.");
|
|
140
|
+
const resolvedIntents = resolveGatewayIntents(options.intents);
|
|
141
|
+
if (!Number.isInteger(resolvedIntents) || resolvedIntents < 0)
|
|
142
|
+
throw new TypeError("Gateway intents must be a non-negative integer.");
|
|
143
|
+
this.#options = {
|
|
144
|
+
shardId: 0,
|
|
145
|
+
shardCount: 1,
|
|
146
|
+
reconnect: true,
|
|
147
|
+
maxReconnectAttempts: Infinity,
|
|
148
|
+
reconnectBaseDelay: 1000,
|
|
149
|
+
reconnectMaxDelay: 30000,
|
|
150
|
+
heartbeatAckTimeout: 1e4,
|
|
151
|
+
zombieTimeout: 30000,
|
|
152
|
+
properties: {
|
|
153
|
+
os: "Android",
|
|
154
|
+
browser: "Discord Android",
|
|
155
|
+
device: "Discord Android"
|
|
156
|
+
},
|
|
157
|
+
presence: { status: "online", activities: [], afk: false, since: null },
|
|
158
|
+
...options
|
|
159
|
+
};
|
|
160
|
+
if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
|
|
161
|
+
throw new RangeError("Gateway shardId must be within the configured shard count.");
|
|
162
|
+
if (this.#options.zombieTimeout <= this.#options.heartbeatAckTimeout)
|
|
163
|
+
throw new RangeError("Gateway zombieTimeout must be greater than heartbeatAckTimeout");
|
|
164
|
+
}
|
|
165
|
+
connect(url = "wss://gateway.discord.gg/?v=10&encoding=json") {
|
|
166
|
+
if (this.state === "CLOSED" /* Closed */)
|
|
167
|
+
throw new GatewayError("Gateway has been permanently closed.");
|
|
168
|
+
if (this.#connectPromise)
|
|
169
|
+
return this.#connectPromise;
|
|
170
|
+
this.#closed = false;
|
|
171
|
+
this.#setState("CONNECT" /* Connect */);
|
|
172
|
+
this.#connectPromise = new Promise((resolve, reject) => {
|
|
173
|
+
this.#resolveConnect = resolve;
|
|
174
|
+
this.#rejectConnect = reject;
|
|
175
|
+
this.#open(url);
|
|
176
|
+
});
|
|
177
|
+
return this.#connectPromise;
|
|
178
|
+
}
|
|
179
|
+
close() {
|
|
180
|
+
this.#closed = true;
|
|
181
|
+
this.#clearTimers();
|
|
182
|
+
this.#settleConnect(new GatewayError("Gateway connection closed before socket open."));
|
|
183
|
+
const ws = this.#ws;
|
|
184
|
+
this.#ws = undefined;
|
|
185
|
+
try {
|
|
186
|
+
ws?.close(1000, "Client closed connection");
|
|
187
|
+
} catch (error) {
|
|
188
|
+
this.#emitError(error);
|
|
189
|
+
}
|
|
190
|
+
this.#setState("CLOSED" /* Closed */);
|
|
191
|
+
}
|
|
192
|
+
emit(event, data) {
|
|
193
|
+
this.#emit(event, data);
|
|
194
|
+
}
|
|
195
|
+
on(event, listener) {
|
|
196
|
+
if (!event || typeof listener !== "function")
|
|
197
|
+
throw new TypeError("Gateway event and listener are required.");
|
|
198
|
+
let listeners = this.#listeners.get(event);
|
|
199
|
+
if (!listeners)
|
|
200
|
+
this.#listeners.set(event, listeners = new Set);
|
|
201
|
+
listeners.add(listener);
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
off(event, listener) {
|
|
205
|
+
this.#listeners.get(event)?.delete(listener);
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
send(payload) {
|
|
209
|
+
if (!payload || !Number.isInteger(payload.op))
|
|
210
|
+
throw new TypeError("Gateway payload must contain an integer opcode.");
|
|
211
|
+
if (this.#ws?.readyState !== WebSocket.OPEN)
|
|
212
|
+
return false;
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
while (this.#sendTimestamps.length && now - this.#sendTimestamps[0] >= 60000)
|
|
215
|
+
this.#sendTimestamps.shift();
|
|
216
|
+
if (this.#sendTimestamps.length >= 115) {
|
|
217
|
+
this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
this.#ws.send(JSON.stringify(payload));
|
|
222
|
+
this.#sendTimestamps.push(now);
|
|
223
|
+
return true;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
this.#emitError(error);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setPresence(data) {
|
|
230
|
+
this.#options.presence = { ...this.#options.presence, ...data };
|
|
231
|
+
const payload = {
|
|
232
|
+
since: this.#options.presence.since ?? null,
|
|
233
|
+
activities: this.#options.presence.activities ?? [],
|
|
234
|
+
status: this.#options.presence.status ?? "online",
|
|
235
|
+
afk: Boolean(this.#options.presence.afk)
|
|
236
|
+
};
|
|
237
|
+
return this.send({
|
|
238
|
+
op: GatewayOpcodes.PresenceUpdate,
|
|
239
|
+
d: payload,
|
|
240
|
+
s: null,
|
|
241
|
+
t: null
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
setVoiceState(data) {
|
|
245
|
+
return this.send({
|
|
246
|
+
op: GatewayOpcodes.VoiceStateUpdate,
|
|
247
|
+
d: data,
|
|
248
|
+
s: null,
|
|
249
|
+
t: null
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
requestGuildMembers(data) {
|
|
253
|
+
return this.send({
|
|
254
|
+
op: GatewayOpcodes.RequestGuildMembers,
|
|
255
|
+
d: data,
|
|
256
|
+
s: null,
|
|
257
|
+
t: null
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
#open(url) {
|
|
261
|
+
if (this.#closed)
|
|
262
|
+
return;
|
|
263
|
+
let ws;
|
|
264
|
+
try {
|
|
265
|
+
ws = new WebSocket(url);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
const failure = this.#normalizeError(error);
|
|
268
|
+
this.#emitError(failure);
|
|
269
|
+
if (this.#options.reconnect) {
|
|
270
|
+
this.#setState("RECONNECT" /* Reconnect */);
|
|
271
|
+
this.#scheduleReconnect();
|
|
272
|
+
} else
|
|
273
|
+
this.#settleConnect(failure);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
this.#ws = ws;
|
|
277
|
+
ws.addEventListener("open", () => {
|
|
278
|
+
this.#lastMessageAt = Date.now();
|
|
279
|
+
this.#zombieReported = false;
|
|
280
|
+
this.#startZombieDetection();
|
|
281
|
+
this.#emit("open", undefined);
|
|
282
|
+
this.#settleConnect();
|
|
283
|
+
});
|
|
284
|
+
ws.addEventListener("message", (event) => {
|
|
285
|
+
this.#lastMessageAt = Date.now();
|
|
286
|
+
this.#zombieReported = false;
|
|
287
|
+
this.#message(ws, String(event.data));
|
|
288
|
+
});
|
|
289
|
+
ws.addEventListener("close", (event) => this.#close(ws, event.code));
|
|
290
|
+
ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
|
|
291
|
+
}
|
|
292
|
+
#startZombieDetection() {
|
|
293
|
+
if (this.#zombieTimer)
|
|
294
|
+
clearInterval(this.#zombieTimer);
|
|
295
|
+
const interval = Math.max(1, Math.min(250, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
|
|
296
|
+
this.#zombieTimer = setInterval(() => {
|
|
297
|
+
if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
|
|
298
|
+
return;
|
|
299
|
+
const silentFor = Date.now() - this.#lastMessageAt;
|
|
300
|
+
const deadline = Math.max(this.#options.zombieTimeout, this.#heartbeatInterval + this.#options.heartbeatAckTimeout);
|
|
301
|
+
if (silentFor < deadline || this.#zombieReported)
|
|
302
|
+
return;
|
|
303
|
+
this.#zombieReported = true;
|
|
304
|
+
const error = new GatewayError(`Gateway connection appears stale after ${silentFor}ms without traffic.`);
|
|
305
|
+
this.#emit("zombie", { silentFor, timeout: deadline });
|
|
306
|
+
this.#emitError(error);
|
|
307
|
+
try {
|
|
308
|
+
this.#ws.close(1001, "Zombie Gateway connection");
|
|
309
|
+
} catch (closeError) {
|
|
310
|
+
this.#emitError(closeError);
|
|
311
|
+
}
|
|
312
|
+
}, interval);
|
|
313
|
+
}
|
|
314
|
+
#message(ws, raw) {
|
|
315
|
+
let payload;
|
|
316
|
+
try {
|
|
317
|
+
payload = JSON.parse(raw);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
this.#emitError(new GatewayError("Gateway returned invalid JSON", undefined, {
|
|
320
|
+
cause: error
|
|
321
|
+
}));
|
|
322
|
+
ws.close(1002, "Invalid JSON");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!payload || typeof payload.op !== "number") {
|
|
326
|
+
this.#emitError(new GatewayError("Gateway returned an invalid payload"));
|
|
327
|
+
ws.close(1002, "Invalid payload");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (typeof payload.s === "number")
|
|
331
|
+
this.#sequence = payload.s;
|
|
332
|
+
switch (payload.op) {
|
|
333
|
+
case GatewayOpcodes.Dispatch:
|
|
334
|
+
this.#setState("DISPATCH" /* Dispatch */);
|
|
335
|
+
this.#handleDispatch(payload.t, payload.d);
|
|
336
|
+
break;
|
|
337
|
+
case GatewayOpcodes.Hello:
|
|
338
|
+
this.#handleHello(payload.d);
|
|
339
|
+
break;
|
|
340
|
+
case GatewayOpcodes.Heartbeat:
|
|
341
|
+
this.#setState("HEARTBEAT" /* Heartbeat */);
|
|
342
|
+
this.#sendHeartbeat();
|
|
343
|
+
break;
|
|
344
|
+
case GatewayOpcodes.HeartbeatAck:
|
|
345
|
+
this.#heartbeatACK = true;
|
|
346
|
+
this.#clearHeartbeatAckTimer();
|
|
347
|
+
this.#emit("heartbeatAck", payload.d);
|
|
348
|
+
break;
|
|
349
|
+
case GatewayOpcodes.Reconnect:
|
|
350
|
+
ws.close(1001, "Server requested reconnect");
|
|
351
|
+
break;
|
|
352
|
+
case GatewayOpcodes.InvalidSession:
|
|
353
|
+
this.#sessionId = undefined;
|
|
354
|
+
this.#sequence = null;
|
|
355
|
+
this.#emit("invalidSession", payload.d);
|
|
356
|
+
ws.close(1000, "Invalid session");
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
#handleDispatch(event, data) {
|
|
361
|
+
if (event === "READY") {
|
|
362
|
+
const ready = data;
|
|
363
|
+
if (typeof ready?.session_id !== "string" || typeof ready?.resume_gateway_url !== "string") {
|
|
364
|
+
this.#emitError(new GatewayError("Gateway READY payload is missing session information"));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
this.#sessionId = ready.session_id;
|
|
368
|
+
this.#resumeURL = ready.resume_gateway_url;
|
|
369
|
+
this.#attempt = 0;
|
|
370
|
+
this.#setState("READY" /* Ready */);
|
|
371
|
+
this.#emit("ready", data);
|
|
372
|
+
}
|
|
373
|
+
this.#emit(event ?? "dispatch", data);
|
|
374
|
+
}
|
|
375
|
+
#handleHello(data) {
|
|
376
|
+
const interval = data?.heartbeat_interval;
|
|
377
|
+
if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0) {
|
|
378
|
+
this.#emitError(new GatewayError("Gateway HELLO payload contains an invalid heartbeat interval"));
|
|
379
|
+
this.#ws?.close(1002, "Invalid heartbeat interval");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
this.#heartbeatInterval = interval;
|
|
383
|
+
this.#setState(this.#sessionId && this.#sequence !== null && this.#resumeURL ? "RESUME" /* Resume */ : "HELLO" /* Hello */);
|
|
384
|
+
this.#startHeartbeat(interval);
|
|
385
|
+
this.#identifyOrResume();
|
|
386
|
+
}
|
|
387
|
+
#identifyOrResume() {
|
|
388
|
+
if (this.#sessionId && this.#sequence !== null && this.#resumeURL) {
|
|
389
|
+
this.#setState("RESUME" /* Resume */);
|
|
390
|
+
this.send({
|
|
391
|
+
op: GatewayOpcodes.Resume,
|
|
392
|
+
d: {
|
|
393
|
+
token: this.#options.token,
|
|
394
|
+
session_id: this.#sessionId,
|
|
395
|
+
seq: this.#sequence
|
|
396
|
+
},
|
|
397
|
+
s: null,
|
|
398
|
+
t: null
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
this.#setState("IDENTIFY" /* Identify */);
|
|
403
|
+
const props = this.#options.properties ?? {
|
|
404
|
+
os: "Android",
|
|
405
|
+
browser: "Discord Android",
|
|
406
|
+
device: "Discord Android"
|
|
407
|
+
};
|
|
408
|
+
const os = props.os ?? "Android";
|
|
409
|
+
const browser = props.browser ?? "Discord Android";
|
|
410
|
+
const device = props.device ?? "Discord Android";
|
|
411
|
+
this.send({
|
|
412
|
+
op: GatewayOpcodes.Identify,
|
|
413
|
+
d: {
|
|
414
|
+
token: this.#options.token,
|
|
415
|
+
intents: this.#options.intents,
|
|
416
|
+
properties: {
|
|
417
|
+
os,
|
|
418
|
+
browser,
|
|
419
|
+
device,
|
|
420
|
+
$os: os,
|
|
421
|
+
$browser: browser,
|
|
422
|
+
$device: device,
|
|
423
|
+
...props
|
|
424
|
+
},
|
|
425
|
+
presence: {
|
|
426
|
+
since: this.#options.presence?.since ?? null,
|
|
427
|
+
activities: this.#options.presence?.activities ?? [],
|
|
428
|
+
status: this.#options.presence?.status ?? "online",
|
|
429
|
+
afk: Boolean(this.#options.presence?.afk)
|
|
430
|
+
},
|
|
431
|
+
shard: [this.#options.shardId, this.#options.shardCount]
|
|
432
|
+
},
|
|
433
|
+
s: null,
|
|
434
|
+
t: null
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
#startHeartbeat(interval) {
|
|
438
|
+
this.#clearHeartbeatTimers();
|
|
439
|
+
this.#heartbeatACK = true;
|
|
440
|
+
this.#initialHeartbeat = setTimeout(() => this.#sendHeartbeat(), Math.random() * interval);
|
|
441
|
+
this.#heartbeatTimer = setInterval(() => this.#sendHeartbeat(), interval);
|
|
442
|
+
}
|
|
443
|
+
#sendHeartbeat() {
|
|
444
|
+
this.#heartbeatACK = false;
|
|
445
|
+
this.#heartbeatSentAt = Date.now();
|
|
446
|
+
if (!this.send({
|
|
447
|
+
op: GatewayOpcodes.Heartbeat,
|
|
448
|
+
d: this.#sequence,
|
|
449
|
+
s: null,
|
|
450
|
+
t: null
|
|
451
|
+
})) {
|
|
452
|
+
this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
this.#clearHeartbeatAckTimer();
|
|
456
|
+
this.#heartbeatAckTimer = setTimeout(() => {
|
|
457
|
+
if (!this.#heartbeatACK) {
|
|
458
|
+
const elapsed = Date.now() - this.#heartbeatSentAt;
|
|
459
|
+
this.#emitError(new GatewayError(`Gateway heartbeat acknowledgement timed out after ${elapsed}ms.`));
|
|
460
|
+
try {
|
|
461
|
+
this.#ws?.close(1001, "Heartbeat timeout");
|
|
462
|
+
} catch (error) {
|
|
463
|
+
this.#emitError(error);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}, this.#options.heartbeatAckTimeout);
|
|
467
|
+
}
|
|
468
|
+
#close(ws, code) {
|
|
469
|
+
if (this.#ws !== ws)
|
|
470
|
+
return;
|
|
471
|
+
this.#ws = undefined;
|
|
472
|
+
this.#clearTimers();
|
|
473
|
+
const action = this.#closeAction(code);
|
|
474
|
+
this.#emit("close", { code, action });
|
|
475
|
+
if (this.#closed || !this.#options.reconnect || action === "stop") {
|
|
476
|
+
this.#setState(this.#closed ? "CLOSED" /* Closed */ : "CONNECT" /* Connect */);
|
|
477
|
+
if (this.#connectPromise)
|
|
478
|
+
this.#settleConnect(new GatewayError(`Gateway closed before READY (code ${code}).`, code));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (action === "identify") {
|
|
482
|
+
this.#sequence = null;
|
|
483
|
+
this.#sessionId = undefined;
|
|
484
|
+
}
|
|
485
|
+
this.#setState("RECONNECT" /* Reconnect */);
|
|
486
|
+
this.#scheduleReconnect();
|
|
487
|
+
}
|
|
488
|
+
#closeAction(code) {
|
|
489
|
+
if ([4004, 4010, 4011, 4012, 4013, 4014].includes(code))
|
|
490
|
+
return "stop";
|
|
491
|
+
if ([4007, 4009].includes(code))
|
|
492
|
+
return "identify";
|
|
493
|
+
return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
|
|
494
|
+
}
|
|
495
|
+
#scheduleReconnect() {
|
|
496
|
+
if (this.#closed || !this.#options.reconnect || this.#reconnectTimer)
|
|
497
|
+
return;
|
|
498
|
+
if (this.#attempt >= this.#options.maxReconnectAttempts) {
|
|
499
|
+
this.#settleConnect(new GatewayError("Gateway reconnect attempts exhausted."));
|
|
500
|
+
this.#setState("CONNECT" /* Connect */);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const delay = Math.min(this.#options.reconnectMaxDelay, this.#options.reconnectBaseDelay * 2 ** this.#attempt++);
|
|
504
|
+
const jitter = Math.random() * Math.min(250, Math.max(1, delay * 0.25));
|
|
505
|
+
this.#reconnectTimer = setTimeout(() => {
|
|
506
|
+
this.#reconnectTimer = undefined;
|
|
507
|
+
this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
|
|
508
|
+
}, delay + jitter);
|
|
509
|
+
}
|
|
510
|
+
#settleConnect(error) {
|
|
511
|
+
const resolve = this.#resolveConnect;
|
|
512
|
+
const reject = this.#rejectConnect;
|
|
513
|
+
this.#resolveConnect = undefined;
|
|
514
|
+
this.#rejectConnect = undefined;
|
|
515
|
+
this.#connectPromise = undefined;
|
|
516
|
+
if (error)
|
|
517
|
+
reject?.(error);
|
|
518
|
+
else
|
|
519
|
+
resolve?.();
|
|
520
|
+
}
|
|
521
|
+
#clearHeartbeatAckTimer() {
|
|
522
|
+
if (this.#heartbeatAckTimer)
|
|
523
|
+
clearTimeout(this.#heartbeatAckTimer);
|
|
524
|
+
this.#heartbeatAckTimer = undefined;
|
|
525
|
+
}
|
|
526
|
+
#clearHeartbeatTimers() {
|
|
527
|
+
if (this.#heartbeatTimer)
|
|
528
|
+
clearInterval(this.#heartbeatTimer);
|
|
529
|
+
if (this.#initialHeartbeat)
|
|
530
|
+
clearTimeout(this.#initialHeartbeat);
|
|
531
|
+
this.#clearHeartbeatAckTimer();
|
|
532
|
+
this.#heartbeatTimer = undefined;
|
|
533
|
+
this.#initialHeartbeat = undefined;
|
|
534
|
+
}
|
|
535
|
+
#clearTimers() {
|
|
536
|
+
if (this.#reconnectTimer)
|
|
537
|
+
clearTimeout(this.#reconnectTimer);
|
|
538
|
+
if (this.#zombieTimer)
|
|
539
|
+
clearInterval(this.#zombieTimer);
|
|
540
|
+
this.#reconnectTimer = undefined;
|
|
541
|
+
this.#zombieTimer = undefined;
|
|
542
|
+
this.#clearHeartbeatTimers();
|
|
543
|
+
}
|
|
544
|
+
#normalizeError(error) {
|
|
545
|
+
return error instanceof GatewayError ? error : new GatewayError("Gateway connection failed.", undefined, {
|
|
546
|
+
cause: error
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
#emitError(error) {
|
|
550
|
+
this.#emit("error", error instanceof Error ? error : new GatewayError(String(error)));
|
|
551
|
+
}
|
|
552
|
+
#emit(event, data) {
|
|
553
|
+
for (const listener of this.#listeners.get(event) ?? []) {
|
|
554
|
+
try {
|
|
555
|
+
const result = listener(data);
|
|
556
|
+
if (result && typeof result.then === "function")
|
|
557
|
+
Promise.resolve(result).catch((error) => {
|
|
558
|
+
if (event !== "error")
|
|
559
|
+
this.#emitError(error);
|
|
560
|
+
});
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (event !== "error")
|
|
563
|
+
this.#emitError(error);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
#setState(next) {
|
|
568
|
+
if (this.state === next)
|
|
569
|
+
return;
|
|
570
|
+
const previous = this.state;
|
|
571
|
+
this.state = next;
|
|
572
|
+
this.#emit("stateChange", { previous, next });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
export {
|
|
576
|
+
Gateway,
|
|
577
|
+
GatewayError,
|
|
578
|
+
GatewayOpcodes,
|
|
579
|
+
GatewayState
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
//# debugId=8F34B5651398B33564756E2164756E21
|