discolink 1.0.0
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 +201 -0
- package/NOTICE +6 -0
- package/README.md +53 -0
- package/lib/index.d.mts +1789 -0
- package/lib/index.d.ts +1789 -0
- package/lib/index.js +2198 -0
- package/lib/index.mjs +2158 -0
- package/package.json +71 -0
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,2158 @@
|
|
|
1
|
+
import { URL } from 'node:url';
|
|
2
|
+
import { validateHeaderValue } from 'node:http';
|
|
3
|
+
import Axios, { HttpStatusCode } from 'axios';
|
|
4
|
+
import { EventEmitter, once } from 'node:events';
|
|
5
|
+
import { clearTimeout, setTimeout } from 'node:timers';
|
|
6
|
+
import { WebSocket } from 'ws';
|
|
7
|
+
import { setTimeout as setTimeout$1 } from 'node:timers/promises';
|
|
8
|
+
|
|
9
|
+
// src/Typings/API/Rest.ts
|
|
10
|
+
var LoadType = /* @__PURE__ */ ((LoadType2) => {
|
|
11
|
+
LoadType2["Track"] = "track";
|
|
12
|
+
LoadType2["Playlist"] = "playlist";
|
|
13
|
+
LoadType2["Search"] = "search";
|
|
14
|
+
LoadType2["Empty"] = "empty";
|
|
15
|
+
LoadType2["Error"] = "error";
|
|
16
|
+
return LoadType2;
|
|
17
|
+
})(LoadType || {});
|
|
18
|
+
var RoutePlannerType = /* @__PURE__ */ ((RoutePlannerType2) => {
|
|
19
|
+
RoutePlannerType2["Rotating"] = "RotatingIpRoutePlanner";
|
|
20
|
+
RoutePlannerType2["Nano"] = "NanoIpRoutePlanner";
|
|
21
|
+
RoutePlannerType2["RotatingNano"] = "RotatingNanoIpRoutePlanner";
|
|
22
|
+
RoutePlannerType2["Balancing"] = "BalancingIpRoutePlanner";
|
|
23
|
+
return RoutePlannerType2;
|
|
24
|
+
})(RoutePlannerType || {});
|
|
25
|
+
var IPBlockType = /* @__PURE__ */ ((IPBlockType2) => {
|
|
26
|
+
IPBlockType2["V4"] = "Inet4Address";
|
|
27
|
+
IPBlockType2["V6"] = "Inet6Address";
|
|
28
|
+
return IPBlockType2;
|
|
29
|
+
})(IPBlockType || {});
|
|
30
|
+
|
|
31
|
+
// src/Typings/API/Websocket.ts
|
|
32
|
+
var Severity = /* @__PURE__ */ ((Severity2) => {
|
|
33
|
+
Severity2["Common"] = "common";
|
|
34
|
+
Severity2["Suspicious"] = "suspicious";
|
|
35
|
+
Severity2["Fault"] = "fault";
|
|
36
|
+
return Severity2;
|
|
37
|
+
})(Severity || {});
|
|
38
|
+
var OPType = /* @__PURE__ */ ((OPType2) => {
|
|
39
|
+
OPType2["Ready"] = "ready";
|
|
40
|
+
OPType2["PlayerUpdate"] = "playerUpdate";
|
|
41
|
+
OPType2["Stats"] = "stats";
|
|
42
|
+
OPType2["Event"] = "event";
|
|
43
|
+
return OPType2;
|
|
44
|
+
})(OPType || {});
|
|
45
|
+
var EventType = /* @__PURE__ */ ((EventType2) => {
|
|
46
|
+
EventType2["TrackStart"] = "TrackStartEvent";
|
|
47
|
+
EventType2["TrackEnd"] = "TrackEndEvent";
|
|
48
|
+
EventType2["TrackException"] = "TrackExceptionEvent";
|
|
49
|
+
EventType2["TrackStuck"] = "TrackStuckEvent";
|
|
50
|
+
EventType2["WebSocketClosed"] = "WebSocketClosedEvent";
|
|
51
|
+
return EventType2;
|
|
52
|
+
})(EventType || {});
|
|
53
|
+
var TrackEndReason = /* @__PURE__ */ ((TrackEndReason2) => {
|
|
54
|
+
TrackEndReason2["Finished"] = "finished";
|
|
55
|
+
TrackEndReason2["LoadFailed"] = "loadFailed";
|
|
56
|
+
TrackEndReason2["Stopped"] = "stopped";
|
|
57
|
+
TrackEndReason2["Replaced"] = "replaced";
|
|
58
|
+
TrackEndReason2["Cleanup"] = "cleanup";
|
|
59
|
+
return TrackEndReason2;
|
|
60
|
+
})(TrackEndReason || {});
|
|
61
|
+
|
|
62
|
+
// src/Typings/Node/Node.ts
|
|
63
|
+
var CloseCodes = /* @__PURE__ */ ((CloseCodes2) => {
|
|
64
|
+
CloseCodes2[CloseCodes2["Normal"] = 1e3] = "Normal";
|
|
65
|
+
CloseCodes2[CloseCodes2["GoingAway"] = 1001] = "GoingAway";
|
|
66
|
+
CloseCodes2[CloseCodes2["ProtocolError"] = 1002] = "ProtocolError";
|
|
67
|
+
CloseCodes2[CloseCodes2["UnsupportedData"] = 1003] = "UnsupportedData";
|
|
68
|
+
CloseCodes2[CloseCodes2["Reserved"] = 1004] = "Reserved";
|
|
69
|
+
CloseCodes2[CloseCodes2["NoStatusReceived"] = 1005] = "NoStatusReceived";
|
|
70
|
+
CloseCodes2[CloseCodes2["Abnormal"] = 1006] = "Abnormal";
|
|
71
|
+
CloseCodes2[CloseCodes2["InvalidFramePayloadData"] = 1007] = "InvalidFramePayloadData";
|
|
72
|
+
CloseCodes2[CloseCodes2["PolicyViolation"] = 1008] = "PolicyViolation";
|
|
73
|
+
CloseCodes2[CloseCodes2["MessageTooBig"] = 1009] = "MessageTooBig";
|
|
74
|
+
CloseCodes2[CloseCodes2["MandatoryExtension"] = 1010] = "MandatoryExtension";
|
|
75
|
+
CloseCodes2[CloseCodes2["InternalError"] = 1011] = "InternalError";
|
|
76
|
+
CloseCodes2[CloseCodes2["ServiceRestart"] = 1012] = "ServiceRestart";
|
|
77
|
+
CloseCodes2[CloseCodes2["TryAgainLater"] = 1013] = "TryAgainLater";
|
|
78
|
+
CloseCodes2[CloseCodes2["BadGateway"] = 1014] = "BadGateway";
|
|
79
|
+
CloseCodes2[CloseCodes2["TLSHandshake"] = 1015] = "TLSHandshake";
|
|
80
|
+
return CloseCodes2;
|
|
81
|
+
})(CloseCodes || {});
|
|
82
|
+
|
|
83
|
+
// src/Typings/Queue/QueueManager.ts
|
|
84
|
+
var VoiceCloseCodes = /* @__PURE__ */ ((VoiceCloseCodes2) => {
|
|
85
|
+
VoiceCloseCodes2[VoiceCloseCodes2["UnknownOpcode"] = 4001] = "UnknownOpcode";
|
|
86
|
+
VoiceCloseCodes2[VoiceCloseCodes2["FailedToDecodePayload"] = 4002] = "FailedToDecodePayload";
|
|
87
|
+
VoiceCloseCodes2[VoiceCloseCodes2["NotAuthenticated"] = 4003] = "NotAuthenticated";
|
|
88
|
+
VoiceCloseCodes2[VoiceCloseCodes2["AuthenticationFailed"] = 4004] = "AuthenticationFailed";
|
|
89
|
+
VoiceCloseCodes2[VoiceCloseCodes2["AlreadyAuthenticated"] = 4005] = "AlreadyAuthenticated";
|
|
90
|
+
VoiceCloseCodes2[VoiceCloseCodes2["SessionNoLongerValid"] = 4006] = "SessionNoLongerValid";
|
|
91
|
+
VoiceCloseCodes2[VoiceCloseCodes2["SessionTimeout"] = 4009] = "SessionTimeout";
|
|
92
|
+
VoiceCloseCodes2[VoiceCloseCodes2["ServerNotFound"] = 4011] = "ServerNotFound";
|
|
93
|
+
VoiceCloseCodes2[VoiceCloseCodes2["UnknownProtocol"] = 4012] = "UnknownProtocol";
|
|
94
|
+
VoiceCloseCodes2[VoiceCloseCodes2["Disconnected"] = 4014] = "Disconnected";
|
|
95
|
+
VoiceCloseCodes2[VoiceCloseCodes2["VoiceServerCrashed"] = 4015] = "VoiceServerCrashed";
|
|
96
|
+
VoiceCloseCodes2[VoiceCloseCodes2["UnknownEncryptionMode"] = 4016] = "UnknownEncryptionMode";
|
|
97
|
+
VoiceCloseCodes2[VoiceCloseCodes2["BadRequest"] = 4020] = "BadRequest";
|
|
98
|
+
VoiceCloseCodes2[VoiceCloseCodes2["DisconnectedRateLimited"] = 4021] = "DisconnectedRateLimited";
|
|
99
|
+
VoiceCloseCodes2[VoiceCloseCodes2["DisconnectedCallTerminated"] = 4022] = "DisconnectedCallTerminated";
|
|
100
|
+
return VoiceCloseCodes2;
|
|
101
|
+
})(VoiceCloseCodes || {});
|
|
102
|
+
|
|
103
|
+
// src/Constants/NodeOptions.ts
|
|
104
|
+
var DefaultNodeOptions = Object.seal({
|
|
105
|
+
statsInterval: 6e4,
|
|
106
|
+
highestLatency: 2e3,
|
|
107
|
+
reconnectDelay: 1e4,
|
|
108
|
+
reconnectLimit: 3,
|
|
109
|
+
handshakeTimeout: 5e3
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// src/Constants/PlayerOptions.ts
|
|
113
|
+
var DefaultPlayerOptions = Object.seal({
|
|
114
|
+
queryPrefix: "ytsearch",
|
|
115
|
+
relocateQueues: true,
|
|
116
|
+
async fetchRelatedTracks() {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// src/Constants/Regex.ts
|
|
122
|
+
var SnowflakeRegex = /^\d{17,20}$/;
|
|
123
|
+
var VoiceRegionIdRegex = /^((-?[a-z]+)+)(?=[a-z\d-]*\.discord\.media:\d+$)/;
|
|
124
|
+
|
|
125
|
+
// src/Constants/RESTOptions.ts
|
|
126
|
+
var DefaultRestOptions = Object.seal({
|
|
127
|
+
version: 4,
|
|
128
|
+
userAgent: "discolink/1.0.0 (https://github.com/execaman/discolink)",
|
|
129
|
+
stackTrace: false,
|
|
130
|
+
retryLimit: 0,
|
|
131
|
+
requestTimeout: 15e3
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// src/Constants/Routes.ts
|
|
135
|
+
var Routes = {
|
|
136
|
+
websocket() {
|
|
137
|
+
return "/websocket";
|
|
138
|
+
},
|
|
139
|
+
trackLoading() {
|
|
140
|
+
return "/loadtracks";
|
|
141
|
+
},
|
|
142
|
+
trackDecoding(multiple) {
|
|
143
|
+
if (multiple) return "/decodetracks";
|
|
144
|
+
return "/decodetrack";
|
|
145
|
+
},
|
|
146
|
+
player(sessionId, guildId) {
|
|
147
|
+
if (guildId) return `/sessions/${sessionId}/players/${guildId}`;
|
|
148
|
+
return `/sessions/${sessionId}/players`;
|
|
149
|
+
},
|
|
150
|
+
session(sessionId) {
|
|
151
|
+
return `/sessions/${sessionId}`;
|
|
152
|
+
},
|
|
153
|
+
info() {
|
|
154
|
+
return "/info";
|
|
155
|
+
},
|
|
156
|
+
stats() {
|
|
157
|
+
return "/stats";
|
|
158
|
+
},
|
|
159
|
+
routePlanner(free) {
|
|
160
|
+
if (free) return `/routeplanner/free/${free}`;
|
|
161
|
+
return "/routeplanner/status";
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/Functions/formatting.ts
|
|
166
|
+
function formatDuration(ms) {
|
|
167
|
+
ms = Math.floor(ms);
|
|
168
|
+
if (ms <= 0 || !Number.isFinite(ms)) return "00:00";
|
|
169
|
+
const ss = Math.floor(ms / 1e3) % 60;
|
|
170
|
+
const mm = Math.floor(ms / (60 * 1e3)) % 60;
|
|
171
|
+
const hh = Math.floor(ms / (60 * 60 * 1e3));
|
|
172
|
+
const mm_ss = `${mm < 10 ? `0${mm}` : mm}:${ss < 10 ? `0${ss}` : ss}`;
|
|
173
|
+
if (hh === 0) return mm_ss;
|
|
174
|
+
return `${hh < 10 ? `0${hh}` : hh}:${mm_ss}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/Functions/utility.ts
|
|
178
|
+
function noop() {
|
|
179
|
+
}
|
|
180
|
+
function isNumber(input, type) {
|
|
181
|
+
if (type === void 0) return Number.isFinite(input);
|
|
182
|
+
switch (type) {
|
|
183
|
+
case "integer":
|
|
184
|
+
return Number.isSafeInteger(input) && !Object.is(input, -0);
|
|
185
|
+
case "natural":
|
|
186
|
+
return Number.isSafeInteger(input) && input > 0;
|
|
187
|
+
case "whole":
|
|
188
|
+
return Number.isSafeInteger(input) && input > -1 && !Object.is(input, -0);
|
|
189
|
+
default:
|
|
190
|
+
throw new SyntaxError(`Invalid number check type '${type}'`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function isString(input, check) {
|
|
194
|
+
if (check === void 0) return typeof input === "string";
|
|
195
|
+
if (typeof input !== "string") return false;
|
|
196
|
+
if (check instanceof RegExp) return check.test(input);
|
|
197
|
+
switch (check) {
|
|
198
|
+
case "url": {
|
|
199
|
+
try {
|
|
200
|
+
return new URL(input).origin !== "null";
|
|
201
|
+
} catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
case "non-empty":
|
|
206
|
+
return input.trim().length !== 0;
|
|
207
|
+
default:
|
|
208
|
+
throw new SyntaxError(`Invalid string check type '${check}'`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function isRecord(input, type) {
|
|
212
|
+
if (type === void 0) return input !== null && typeof input === "object" && !Array.isArray(input);
|
|
213
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) return false;
|
|
214
|
+
switch (type) {
|
|
215
|
+
case "non-empty":
|
|
216
|
+
return Object.keys(input).length !== 0;
|
|
217
|
+
default:
|
|
218
|
+
throw new SyntaxError(`Invalid object check type '${type}'`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function isArray(input, check) {
|
|
222
|
+
if (check === void 0) return Array.isArray(input);
|
|
223
|
+
if (!Array.isArray(input)) return false;
|
|
224
|
+
switch (check) {
|
|
225
|
+
case "string":
|
|
226
|
+
if (input.length === 0) return false;
|
|
227
|
+
return input.every((i) => typeof i === "string" && i.length !== 0);
|
|
228
|
+
case "non-empty":
|
|
229
|
+
return input.length !== 0;
|
|
230
|
+
default:
|
|
231
|
+
throw new SyntaxError(`Invalid array check type '${check}'`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
var REST = class {
|
|
235
|
+
#axios = Axios.create({
|
|
236
|
+
headers: {
|
|
237
|
+
Accept: "application/json"
|
|
238
|
+
},
|
|
239
|
+
transitional: {
|
|
240
|
+
silentJSONParsing: false,
|
|
241
|
+
clarifyTimeoutError: true
|
|
242
|
+
},
|
|
243
|
+
responseType: "json"
|
|
244
|
+
});
|
|
245
|
+
#queue = [];
|
|
246
|
+
#queueIdling = true;
|
|
247
|
+
#sessionId = null;
|
|
248
|
+
#retryLimit;
|
|
249
|
+
origin;
|
|
250
|
+
version;
|
|
251
|
+
constructor(options) {
|
|
252
|
+
const _options = { ...DefaultRestOptions, ...options };
|
|
253
|
+
validateHeaderValue("User-Agent", _options.userAgent);
|
|
254
|
+
validateHeaderValue("Authorization", _options.password);
|
|
255
|
+
const url = new URL(_options.origin);
|
|
256
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
257
|
+
throw new Error("Protocol must be 'http' or 'https'");
|
|
258
|
+
}
|
|
259
|
+
if (typeof _options.stackTrace !== "boolean") {
|
|
260
|
+
throw new Error("Stack trace option must be a boolean");
|
|
261
|
+
}
|
|
262
|
+
if (!isNumber(_options.version, "natural")) {
|
|
263
|
+
throw new Error("Version must be a natural number");
|
|
264
|
+
}
|
|
265
|
+
if (!isNumber(_options.retryLimit, "whole")) {
|
|
266
|
+
throw new Error("Retry limit must be a whole number");
|
|
267
|
+
}
|
|
268
|
+
if (!isNumber(_options.requestTimeout, "natural")) {
|
|
269
|
+
throw new Error("Request timeout must be a natural number");
|
|
270
|
+
}
|
|
271
|
+
if (_options.sessionId !== void 0 && !isString(_options.sessionId, "non-empty")) {
|
|
272
|
+
throw new Error("Session Id must be a non-empty string");
|
|
273
|
+
}
|
|
274
|
+
this.#axios.defaults.timeout = _options.requestTimeout;
|
|
275
|
+
this.#axios.defaults.baseURL = `${url.origin}/v${_options.version}`;
|
|
276
|
+
this.#axios.defaults.headers.common["User-Agent"] = _options.userAgent;
|
|
277
|
+
this.#axios.defaults.headers.common["Authorization"] = _options.password;
|
|
278
|
+
if (_options.stackTrace === true) this.#axios.defaults.params = { trace: true };
|
|
279
|
+
this.origin = url.origin;
|
|
280
|
+
this.version = _options.version;
|
|
281
|
+
this.#retryLimit = _options.retryLimit;
|
|
282
|
+
if (_options.sessionId !== void 0) this.#sessionId = _options.sessionId;
|
|
283
|
+
const immutable = {
|
|
284
|
+
writable: false,
|
|
285
|
+
configurable: false
|
|
286
|
+
};
|
|
287
|
+
Object.defineProperties(this, {
|
|
288
|
+
origin: immutable,
|
|
289
|
+
version: immutable
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
get timeout() {
|
|
293
|
+
return this.#axios.defaults.timeout;
|
|
294
|
+
}
|
|
295
|
+
get sessionId() {
|
|
296
|
+
return this.#sessionId;
|
|
297
|
+
}
|
|
298
|
+
get retryLimit() {
|
|
299
|
+
return this.#retryLimit;
|
|
300
|
+
}
|
|
301
|
+
set timeout(ms) {
|
|
302
|
+
if (isNumber(ms, "natural")) this.#axios.defaults.timeout = ms;
|
|
303
|
+
}
|
|
304
|
+
set sessionId(id) {
|
|
305
|
+
if (id === null || isString(id, "non-empty")) this.#sessionId = id;
|
|
306
|
+
}
|
|
307
|
+
set retryLimit(count) {
|
|
308
|
+
if (isNumber(count, "whole")) this.#retryLimit = count;
|
|
309
|
+
}
|
|
310
|
+
#error(err, path) {
|
|
311
|
+
const res = err.response;
|
|
312
|
+
const data = "errors" in err ? err.errors[err.errors.length - 1] : err;
|
|
313
|
+
const error = new Error(res?.data.message ?? data.message);
|
|
314
|
+
error.name = `Error [${this.constructor.name}]`;
|
|
315
|
+
error.error = res?.data.error ?? res?.statusText ?? "Processing";
|
|
316
|
+
error.path = res?.data.path ?? path;
|
|
317
|
+
error.status = res?.data.status ?? res?.status ?? Axios.HttpStatusCode.Processing;
|
|
318
|
+
error.timestamp = res?.data.timestamp ?? Date.now();
|
|
319
|
+
if (res?.data.trace !== void 0) error.trace = res.data.trace;
|
|
320
|
+
return error;
|
|
321
|
+
}
|
|
322
|
+
async #makeRequest(config, retries = this.#retryLimit) {
|
|
323
|
+
try {
|
|
324
|
+
const response = await this.#axios.request(config);
|
|
325
|
+
return response;
|
|
326
|
+
} catch (err) {
|
|
327
|
+
if (config.signal?.aborted) err.message = "reason" in config.signal ? config.signal.reason : err.message;
|
|
328
|
+
else if (err.code === "ETIMEDOUT" && retries !== 0) return this.#makeRequest(config, retries - 1);
|
|
329
|
+
throw this.#error(err, config.url);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async #resumeQueue() {
|
|
333
|
+
this.#queueIdling = false;
|
|
334
|
+
while (this.#queue.length !== 0) {
|
|
335
|
+
const task = this.#queue[0];
|
|
336
|
+
if (!task.config.signal) {
|
|
337
|
+
task.controller = new AbortController();
|
|
338
|
+
task.config.signal = task.controller.signal;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const response = await this.#makeRequest(task.config);
|
|
342
|
+
task.resolve(response);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
task.reject(err);
|
|
345
|
+
}
|
|
346
|
+
task.controller?.abort();
|
|
347
|
+
this.#queue.shift();
|
|
348
|
+
}
|
|
349
|
+
this.#queueIdling = true;
|
|
350
|
+
}
|
|
351
|
+
dropSessionRequests(reason) {
|
|
352
|
+
if (this.#queue.length === 0) return;
|
|
353
|
+
const tasks = this.#queue.splice(0);
|
|
354
|
+
tasks.shift().controller?.abort(reason);
|
|
355
|
+
if (tasks.length === 0) return;
|
|
356
|
+
const err = { message: reason };
|
|
357
|
+
for (const task of tasks) task.reject(this.#error(err, task.config.url));
|
|
358
|
+
}
|
|
359
|
+
async request(method, endpoint, options) {
|
|
360
|
+
if (!isString(method, "non-empty")) throw new Error("Method must be a non-empty string");
|
|
361
|
+
if (!isString(endpoint, "non-empty")) throw new Error("Endpoint must be a non-empty string");
|
|
362
|
+
const config = { method, url: endpoint };
|
|
363
|
+
if (isRecord(options, "non-empty")) {
|
|
364
|
+
if ("data" in options) {
|
|
365
|
+
config.data = options.data;
|
|
366
|
+
config.headers = { "Content-Type": "application/json" };
|
|
367
|
+
}
|
|
368
|
+
if ("params" in options) {
|
|
369
|
+
if (isRecord(options.params, "non-empty")) config.params = options.params;
|
|
370
|
+
else throw new Error("Options.params must be a non-empty object");
|
|
371
|
+
}
|
|
372
|
+
if ("signal" in options) {
|
|
373
|
+
if (options.signal instanceof AbortSignal && !options.signal.aborted) config.signal = options.signal;
|
|
374
|
+
else throw new Error("Options.signal is either not a instance of AbortSignal or is already aborted");
|
|
375
|
+
}
|
|
376
|
+
if ("timeout" in options) {
|
|
377
|
+
if (isNumber(options.timeout, "natural")) config.timeout = options.timeout;
|
|
378
|
+
else throw new Error("Options.timeout must be a natural number");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (this.#sessionId === null || !endpoint.includes(this.#sessionId)) {
|
|
382
|
+
return this.#makeRequest(config);
|
|
383
|
+
}
|
|
384
|
+
return new Promise((resolve, reject) => {
|
|
385
|
+
this.#queue.push({ config, reject, resolve });
|
|
386
|
+
if (this.#queueIdling) this.#resumeQueue();
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
async loadTracks(identifier) {
|
|
390
|
+
if (!isString(identifier, "non-empty")) throw new Error("Identifier must be a non-empty string");
|
|
391
|
+
const response = await this.request("GET", Routes.trackLoading(), { params: { identifier } });
|
|
392
|
+
return response.data;
|
|
393
|
+
}
|
|
394
|
+
async decodeTrack(encodedTrack) {
|
|
395
|
+
if (!isString(encodedTrack, "non-empty")) throw new Error("Encoded track must be a non-empty string");
|
|
396
|
+
const response = await this.request("GET", Routes.trackDecoding(), { params: { encodedTrack } });
|
|
397
|
+
return response.data;
|
|
398
|
+
}
|
|
399
|
+
async decodeTracks(tracks) {
|
|
400
|
+
if (!isArray(tracks, "string")) throw new Error("Tracks must be an array of non-empty strings");
|
|
401
|
+
const response = await this.request("POST", Routes.trackDecoding(true), { data: tracks });
|
|
402
|
+
return response.data;
|
|
403
|
+
}
|
|
404
|
+
async fetchPlayers(sessionId = this.sessionId) {
|
|
405
|
+
if (!isString(sessionId, "non-empty")) throw new Error("Session Id neither set nor provided");
|
|
406
|
+
const response = await this.request("GET", Routes.player(sessionId));
|
|
407
|
+
return response.data;
|
|
408
|
+
}
|
|
409
|
+
async fetchPlayer(guildId, sessionId = this.sessionId) {
|
|
410
|
+
if (!isString(guildId, "non-empty")) throw new Error("Guild Id must be a non-empty string");
|
|
411
|
+
if (!isString(sessionId, "non-empty")) throw new Error("Session Id neither set nor provided");
|
|
412
|
+
const response = await this.request("GET", Routes.player(sessionId, guildId));
|
|
413
|
+
return response.data;
|
|
414
|
+
}
|
|
415
|
+
async updatePlayer(guildId, options, params, sessionId = this.sessionId) {
|
|
416
|
+
if (!isString(guildId, "non-empty")) throw new Error("Guild Id must be a non-empty string");
|
|
417
|
+
if (!isString(sessionId, "non-empty")) throw new Error("Session Id neither set nor provided");
|
|
418
|
+
if (!isRecord(options, "non-empty")) throw new Error("Player update options cannot be empty");
|
|
419
|
+
const response = await this.request("PATCH", Routes.player(sessionId, guildId), {
|
|
420
|
+
data: options,
|
|
421
|
+
params: { noReplace: params?.noReplace === true }
|
|
422
|
+
});
|
|
423
|
+
return response.data;
|
|
424
|
+
}
|
|
425
|
+
async destroyPlayer(guildId, sessionId = this.sessionId) {
|
|
426
|
+
if (!isString(guildId, "non-empty")) throw new Error("Guild Id must be a non-empty string");
|
|
427
|
+
if (!isString(sessionId, "non-empty")) throw new Error("Session Id neither set nor provided");
|
|
428
|
+
const response = await this.request("DELETE", Routes.player(sessionId, guildId));
|
|
429
|
+
return response.status === Axios.HttpStatusCode.NoContent;
|
|
430
|
+
}
|
|
431
|
+
async updateSession(options, sessionId = this.sessionId) {
|
|
432
|
+
if (!isRecord(options, "non-empty")) throw new Error("Session update options cannot be empty");
|
|
433
|
+
if (!isString(sessionId, "non-empty")) throw new Error("Session Id neither set nor provided");
|
|
434
|
+
const response = await this.request("PATCH", Routes.session(sessionId), {
|
|
435
|
+
data: options
|
|
436
|
+
});
|
|
437
|
+
return response.data;
|
|
438
|
+
}
|
|
439
|
+
async fetchInfo() {
|
|
440
|
+
const response = await this.request("GET", Routes.info());
|
|
441
|
+
return response.data;
|
|
442
|
+
}
|
|
443
|
+
async fetchStats() {
|
|
444
|
+
const response = await this.request("GET", Routes.stats());
|
|
445
|
+
return response.data;
|
|
446
|
+
}
|
|
447
|
+
async fetchRoutePlannerStatus() {
|
|
448
|
+
const response = await this.request("GET", Routes.routePlanner());
|
|
449
|
+
if (response.status === Axios.HttpStatusCode.NoContent) return { class: null, details: null };
|
|
450
|
+
return response.data;
|
|
451
|
+
}
|
|
452
|
+
async unmarkFailedAddress(address) {
|
|
453
|
+
if (!isString(address, "non-empty")) throw new Error("Address must be a non-empty string");
|
|
454
|
+
const response = await this.request("POST", Routes.routePlanner("address"), { data: { address } });
|
|
455
|
+
return response.status === Axios.HttpStatusCode.NoContent;
|
|
456
|
+
}
|
|
457
|
+
async unmarkAllFailedAddresses() {
|
|
458
|
+
const response = await this.request("POST", Routes.routePlanner("all"));
|
|
459
|
+
return response.status === Axios.HttpStatusCode.NoContent;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var Node = class extends EventEmitter {
|
|
463
|
+
#socketConfig = {
|
|
464
|
+
headers: {
|
|
465
|
+
"Client-Name": "discolink/1.0.0"
|
|
466
|
+
},
|
|
467
|
+
perMessageDeflate: false
|
|
468
|
+
};
|
|
469
|
+
#connectPromise = null;
|
|
470
|
+
#disconnectPromise = null;
|
|
471
|
+
#pingTimer = null;
|
|
472
|
+
#reconnectTimer = null;
|
|
473
|
+
#stats = null;
|
|
474
|
+
#socket = null;
|
|
475
|
+
#ping = null;
|
|
476
|
+
#lastPingTime = null;
|
|
477
|
+
#manualDisconnect = false;
|
|
478
|
+
#reconnectInit = false;
|
|
479
|
+
#reconnectAttempts = 0;
|
|
480
|
+
#sessionId = null;
|
|
481
|
+
#socketURL;
|
|
482
|
+
#statsInterval;
|
|
483
|
+
#highestLatency;
|
|
484
|
+
#reconnectDelay;
|
|
485
|
+
#reconnectLimit;
|
|
486
|
+
name;
|
|
487
|
+
rest;
|
|
488
|
+
constructor(options) {
|
|
489
|
+
super({ captureRejections: false });
|
|
490
|
+
const _options = { ...DefaultNodeOptions, ...DefaultRestOptions, ...options };
|
|
491
|
+
if (!isString(_options.name, "non-empty")) {
|
|
492
|
+
throw new Error("Name must be a non-empty string");
|
|
493
|
+
}
|
|
494
|
+
if (!isString(_options.clientId, SnowflakeRegex)) {
|
|
495
|
+
throw new Error("Client Id is not a valid Discord Id");
|
|
496
|
+
}
|
|
497
|
+
if (!isNumber(_options.statsInterval, "natural")) {
|
|
498
|
+
throw new Error("Stats interval must be a natural number");
|
|
499
|
+
}
|
|
500
|
+
if (!isNumber(_options.highestLatency, "natural")) {
|
|
501
|
+
throw new Error("Highest latency must be a natural number");
|
|
502
|
+
}
|
|
503
|
+
if (!isNumber(_options.reconnectDelay, "natural")) {
|
|
504
|
+
throw new Error("Reconnect delay must be a natural number");
|
|
505
|
+
}
|
|
506
|
+
if (!isNumber(_options.reconnectLimit, "natural")) {
|
|
507
|
+
throw new Error("Reconnect limit must be a natural number");
|
|
508
|
+
}
|
|
509
|
+
if (!isNumber(_options.handshakeTimeout, "natural")) {
|
|
510
|
+
throw new Error("Handshake timeout must be a natural number");
|
|
511
|
+
}
|
|
512
|
+
this.#statsInterval = _options.statsInterval;
|
|
513
|
+
this.#highestLatency = _options.highestLatency;
|
|
514
|
+
this.#reconnectDelay = _options.reconnectDelay;
|
|
515
|
+
this.#reconnectLimit = _options.reconnectLimit;
|
|
516
|
+
this.#socketConfig.handshakeTimeout = _options.handshakeTimeout;
|
|
517
|
+
this.name = _options.name;
|
|
518
|
+
this.rest = new REST(_options);
|
|
519
|
+
this.#socketURL = `${this.rest.origin.replace("http", "ws")}/v${this.rest.version}${Routes.websocket()}`;
|
|
520
|
+
this.#socketConfig.headers["User-Id"] = _options.clientId;
|
|
521
|
+
this.#socketConfig.headers["User-Agent"] = _options.userAgent;
|
|
522
|
+
this.#socketConfig.headers["Authorization"] = _options.password;
|
|
523
|
+
if (this.rest.sessionId !== null) this.#socketConfig.headers["Session-Id"] = this.rest.sessionId;
|
|
524
|
+
Object.defineProperty(this.rest, "sessionId", {
|
|
525
|
+
get: () => this.#sessionId,
|
|
526
|
+
set: noop
|
|
527
|
+
});
|
|
528
|
+
const immutable = {
|
|
529
|
+
writable: false,
|
|
530
|
+
configurable: false
|
|
531
|
+
};
|
|
532
|
+
Object.defineProperties(this, {
|
|
533
|
+
name: immutable,
|
|
534
|
+
rest: immutable
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
get clientId() {
|
|
538
|
+
return this.#socketConfig.headers["User-Id"];
|
|
539
|
+
}
|
|
540
|
+
get sessionId() {
|
|
541
|
+
return this.#sessionId;
|
|
542
|
+
}
|
|
543
|
+
get ping() {
|
|
544
|
+
return this.#ping;
|
|
545
|
+
}
|
|
546
|
+
get stats() {
|
|
547
|
+
return this.#stats;
|
|
548
|
+
}
|
|
549
|
+
get state() {
|
|
550
|
+
if (this.connecting) return "connecting";
|
|
551
|
+
if (this.connected) return this.ready ? "ready" : "connected";
|
|
552
|
+
if (this.reconnecting) return "reconnecting";
|
|
553
|
+
return "disconnected";
|
|
554
|
+
}
|
|
555
|
+
get connecting() {
|
|
556
|
+
return this.#socket !== null && this.#socket.readyState === this.#socket.CONNECTING;
|
|
557
|
+
}
|
|
558
|
+
get connected() {
|
|
559
|
+
return this.#socket !== null && this.#socket.readyState === this.#socket.OPEN;
|
|
560
|
+
}
|
|
561
|
+
get ready() {
|
|
562
|
+
return this.#sessionId !== null && this.connected;
|
|
563
|
+
}
|
|
564
|
+
get reconnecting() {
|
|
565
|
+
return this.#socket === null && this.#reconnectTimer !== null;
|
|
566
|
+
}
|
|
567
|
+
get disconnected() {
|
|
568
|
+
return this.#socket === null && !this.reconnecting;
|
|
569
|
+
}
|
|
570
|
+
get reconnectAttempts() {
|
|
571
|
+
return this.#reconnectAttempts;
|
|
572
|
+
}
|
|
573
|
+
get handshakeTimeout() {
|
|
574
|
+
return this.#socketConfig.handshakeTimeout;
|
|
575
|
+
}
|
|
576
|
+
set handshakeTimeout(ms) {
|
|
577
|
+
if (isNumber(ms, "natural")) this.#socketConfig.handshakeTimeout = ms;
|
|
578
|
+
}
|
|
579
|
+
#error(err) {
|
|
580
|
+
const data = "errors" in err ? err.errors[err.errors.length - 1] : err;
|
|
581
|
+
const error = data instanceof Error ? data : new Error(`${data.message ?? data}`);
|
|
582
|
+
error.name = `Error [${this.constructor.name}]`;
|
|
583
|
+
return error;
|
|
584
|
+
}
|
|
585
|
+
#cleanup() {
|
|
586
|
+
this.#socket?.removeAllListeners();
|
|
587
|
+
if (this.#pingTimer !== null) clearTimeout(this.#pingTimer);
|
|
588
|
+
this.#socket = this.#pingTimer = this.#stats = null;
|
|
589
|
+
this.#sessionId = this.#lastPingTime = this.#ping = null;
|
|
590
|
+
}
|
|
591
|
+
#reconnect() {
|
|
592
|
+
this.#reconnectInit = false;
|
|
593
|
+
this.#reconnectTimer?.refresh();
|
|
594
|
+
this.#reconnectTimer ??= setTimeout(() => {
|
|
595
|
+
this.#reconnectInit = true;
|
|
596
|
+
this.connect();
|
|
597
|
+
}, this.#reconnectDelay);
|
|
598
|
+
}
|
|
599
|
+
#stopReconnecting(keepCount = false) {
|
|
600
|
+
if (this.#reconnectTimer === null) return;
|
|
601
|
+
clearTimeout(this.#reconnectTimer);
|
|
602
|
+
this.#reconnectTimer = null;
|
|
603
|
+
this.#reconnectInit = false;
|
|
604
|
+
if (!keepCount) this.#reconnectAttempts = 0;
|
|
605
|
+
}
|
|
606
|
+
#keepAliveAndPing() {
|
|
607
|
+
this.#pingTimer?.refresh();
|
|
608
|
+
this.#pingTimer ??= setTimeout(() => {
|
|
609
|
+
this.#socket?.terminate();
|
|
610
|
+
this.#cleanup();
|
|
611
|
+
this.rest.dropSessionRequests(`Connection to node '${this.name}' was zombie`);
|
|
612
|
+
this.#reconnect();
|
|
613
|
+
}, this.#statsInterval + this.#highestLatency).unref();
|
|
614
|
+
this.#socket?.ping();
|
|
615
|
+
this.#lastPingTime = Date.now();
|
|
616
|
+
}
|
|
617
|
+
#parseMessageData(data) {
|
|
618
|
+
try {
|
|
619
|
+
return JSON.parse(data);
|
|
620
|
+
} catch {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async connect() {
|
|
625
|
+
if (this.#socket !== null) return this.#connectPromise ?? this.connected;
|
|
626
|
+
if (this.reconnecting) {
|
|
627
|
+
this.#reconnectAttempts++;
|
|
628
|
+
if (!this.#reconnectInit) this.#stopReconnecting(true);
|
|
629
|
+
}
|
|
630
|
+
this.#socket = new WebSocket(this.#socketURL, this.#socketConfig);
|
|
631
|
+
this.#socket.once("open", () => {
|
|
632
|
+
this.emit("connect", this.#reconnectAttempts, this.name);
|
|
633
|
+
});
|
|
634
|
+
this.#socket.on("message", (data) => {
|
|
635
|
+
this.#onMessage(data.toString("utf8"));
|
|
636
|
+
});
|
|
637
|
+
this.#socket.on("error", (err) => {
|
|
638
|
+
this.emit("error", this.#error(err), this.name);
|
|
639
|
+
});
|
|
640
|
+
this.#socket.on("close", (code, reason) => {
|
|
641
|
+
this.#onClose(code, reason.toString("utf8"));
|
|
642
|
+
});
|
|
643
|
+
this.#socket.on("pong", () => {
|
|
644
|
+
if (this.#lastPingTime === null) return;
|
|
645
|
+
this.#ping = Math.max(0, Date.now() - this.#lastPingTime);
|
|
646
|
+
});
|
|
647
|
+
const resolver = Promise.withResolvers();
|
|
648
|
+
this.#connectPromise = resolver.promise;
|
|
649
|
+
const controller = new AbortController();
|
|
650
|
+
try {
|
|
651
|
+
await Promise.race([
|
|
652
|
+
once(this.#socket, "open", { signal: controller.signal }),
|
|
653
|
+
once(this.#socket, "close", { signal: controller.signal })
|
|
654
|
+
]);
|
|
655
|
+
return this.connected;
|
|
656
|
+
} catch {
|
|
657
|
+
this.#cleanup();
|
|
658
|
+
return false;
|
|
659
|
+
} finally {
|
|
660
|
+
controller.abort();
|
|
661
|
+
this.#connectPromise = null;
|
|
662
|
+
resolver.resolve(this.connected);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
async disconnect(reason = "disconnected") {
|
|
666
|
+
if (this.#disconnectPromise !== null) return this.#disconnectPromise;
|
|
667
|
+
this.#stopReconnecting();
|
|
668
|
+
if (this.#socket === null) return;
|
|
669
|
+
if (this.connecting) {
|
|
670
|
+
this.#manualDisconnect = true;
|
|
671
|
+
this.#socket.terminate();
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (!this.connected) return;
|
|
675
|
+
this.#manualDisconnect = true;
|
|
676
|
+
this.#disconnectPromise = once(this.#socket, "close").then(noop, noop);
|
|
677
|
+
this.#socket.close(1e3 /* Normal */, reason);
|
|
678
|
+
await this.#disconnectPromise;
|
|
679
|
+
this.#disconnectPromise = null;
|
|
680
|
+
}
|
|
681
|
+
async #onMessage(data) {
|
|
682
|
+
const payload = this.#parseMessageData(data);
|
|
683
|
+
if (payload === null) return;
|
|
684
|
+
if (payload.op === "stats" /* Stats */) {
|
|
685
|
+
this.#stats = payload;
|
|
686
|
+
this.#keepAliveAndPing();
|
|
687
|
+
} else if (payload.op === "ready" /* Ready */) {
|
|
688
|
+
this.#stopReconnecting();
|
|
689
|
+
this.#socketConfig.headers["Session-Id"] = this.#sessionId = payload.sessionId;
|
|
690
|
+
this.emit("ready", payload.resumed, payload.sessionId, this.name);
|
|
691
|
+
}
|
|
692
|
+
this.emit("dispatch", payload, this.name);
|
|
693
|
+
}
|
|
694
|
+
#onClose(code, reason) {
|
|
695
|
+
this.#cleanup();
|
|
696
|
+
this.rest.dropSessionRequests(`Connection to node '${this.name}' closed`);
|
|
697
|
+
if (!this.#manualDisconnect && this.#reconnectAttempts < this.#reconnectLimit) {
|
|
698
|
+
this.#reconnect();
|
|
699
|
+
this.emit("close", code, reason, this.name);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
this.#stopReconnecting();
|
|
703
|
+
delete this.#socketConfig.headers["Session-Id"];
|
|
704
|
+
const byLocal = this.#manualDisconnect;
|
|
705
|
+
this.#manualDisconnect = false;
|
|
706
|
+
this.emit("disconnect", code, reason, byLocal, this.name);
|
|
707
|
+
}
|
|
708
|
+
toString() {
|
|
709
|
+
return this.name;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// src/Node/NodeManager.ts
|
|
714
|
+
var NodeManager = class {
|
|
715
|
+
#player;
|
|
716
|
+
#nodes = /* @__PURE__ */ new Map();
|
|
717
|
+
#cache = /* @__PURE__ */ new Map();
|
|
718
|
+
info = /* @__PURE__ */ new Map();
|
|
719
|
+
constructor(player) {
|
|
720
|
+
this.#player = player;
|
|
721
|
+
Object.defineProperty(this, "info", {
|
|
722
|
+
writable: false,
|
|
723
|
+
configurable: false
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
get size() {
|
|
727
|
+
return this.#nodes.size;
|
|
728
|
+
}
|
|
729
|
+
get cache() {
|
|
730
|
+
return this.#cache;
|
|
731
|
+
}
|
|
732
|
+
get ready() {
|
|
733
|
+
return this.#nodes.values().some((n) => n.ready);
|
|
734
|
+
}
|
|
735
|
+
get(name) {
|
|
736
|
+
return this.#nodes.get(name);
|
|
737
|
+
}
|
|
738
|
+
has(name) {
|
|
739
|
+
return this.#nodes.has(name);
|
|
740
|
+
}
|
|
741
|
+
keys() {
|
|
742
|
+
return this.#nodes.keys();
|
|
743
|
+
}
|
|
744
|
+
values() {
|
|
745
|
+
return this.#nodes.values();
|
|
746
|
+
}
|
|
747
|
+
entries() {
|
|
748
|
+
return this.#nodes.entries();
|
|
749
|
+
}
|
|
750
|
+
state(name, equals) {
|
|
751
|
+
const node = this.#nodes.get(name);
|
|
752
|
+
if (!node) throw new Error(`Node '${name}' not found`);
|
|
753
|
+
return equals === void 0 ? node.state : node.state === equals;
|
|
754
|
+
}
|
|
755
|
+
create(options) {
|
|
756
|
+
if (this.#player.clientId === null) throw new Error("Player has not been initialized");
|
|
757
|
+
if (this.#nodes.has(options.name)) throw new Error(`Node '${options.name}' already exists`);
|
|
758
|
+
const node = new Node({ ...options, clientId: this.#player.clientId });
|
|
759
|
+
node.setMaxListeners(1);
|
|
760
|
+
this.#attachEvents(node);
|
|
761
|
+
this.#nodes.set(node.name, node);
|
|
762
|
+
return node;
|
|
763
|
+
}
|
|
764
|
+
delete(name) {
|
|
765
|
+
const node = this.#nodes.get(name);
|
|
766
|
+
if (!node) return false;
|
|
767
|
+
if (!node.disconnected) throw new Error(`Node '${name}' not disconnected`);
|
|
768
|
+
this.#detachEvents(node);
|
|
769
|
+
this.info.delete(name);
|
|
770
|
+
this.#cache.delete(name);
|
|
771
|
+
this.#nodes.delete(name);
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
relevant(weights = { memory: 0.3, workload: 0.2, streaming: 0.5 }) {
|
|
775
|
+
weights.memory = Math.min(1, Math.max(0, weights.memory ?? 0));
|
|
776
|
+
weights.workload = Math.min(1, Math.max(0, weights.workload ?? 0));
|
|
777
|
+
weights.streaming = Math.min(1, Math.max(0, weights.streaming ?? 0));
|
|
778
|
+
const nodes = this.#nodes.values().reduce((list, node) => {
|
|
779
|
+
if (node.ready) list.push(node);
|
|
780
|
+
return list;
|
|
781
|
+
}, []);
|
|
782
|
+
return nodes.sort((a, b) => {
|
|
783
|
+
const metricA = this.#cache.get(a.name);
|
|
784
|
+
const metricB = this.#cache.get(b.name);
|
|
785
|
+
if (metricA && !metricB) return 1;
|
|
786
|
+
if (metricB && !metricA) return -1;
|
|
787
|
+
if (!metricA || !metricB) return 0;
|
|
788
|
+
if (metricA.streaming !== -1 && metricB.streaming === -1) return 1;
|
|
789
|
+
if (metricB.streaming !== -1 && metricA.streaming === -1) return -1;
|
|
790
|
+
return (metricB.memory - metricA.memory) * weights.memory + (metricB.workload - metricA.workload) * weights.workload + (metricB.streaming - metricA.streaming) * weights.streaming;
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
async connect(name) {
|
|
794
|
+
if (typeof name === "string") {
|
|
795
|
+
const node = this.#nodes.get(name);
|
|
796
|
+
if (!node) throw new Error(`Node '${name}' not found`);
|
|
797
|
+
return node.connect();
|
|
798
|
+
}
|
|
799
|
+
for (const node of this.#nodes.values()) await node.connect();
|
|
800
|
+
}
|
|
801
|
+
async disconnect(name) {
|
|
802
|
+
if (typeof name === "string") {
|
|
803
|
+
const node = this.#nodes.get(name);
|
|
804
|
+
if (!node) throw new Error(`Node '${name}' not found`);
|
|
805
|
+
return node.disconnect();
|
|
806
|
+
}
|
|
807
|
+
for (const node of this.#nodes.values()) await node.disconnect();
|
|
808
|
+
}
|
|
809
|
+
async fetchInfo(name, force) {
|
|
810
|
+
if (!this.#nodes.has(name)) throw new Error(`Node '${name}' not found`);
|
|
811
|
+
if (force !== true && this.info.has(name)) return this.info.get(name);
|
|
812
|
+
const info = await this.#nodes.get(name).rest.fetchInfo();
|
|
813
|
+
this.info.set(name, info);
|
|
814
|
+
return info;
|
|
815
|
+
}
|
|
816
|
+
#memory(mem) {
|
|
817
|
+
const available = mem.free / mem.reservable;
|
|
818
|
+
const allocable = (mem.reservable - mem.allocated) / mem.reservable;
|
|
819
|
+
return Math.min(1, available * 0.7 + allocable * 0.3);
|
|
820
|
+
}
|
|
821
|
+
#workload(cpu) {
|
|
822
|
+
if (cpu.systemLoad === 0 || cpu.lavalinkLoad === 0) return 0;
|
|
823
|
+
return 1 - Math.min(1, cpu.systemLoad * 0.7 + cpu.lavalinkLoad * 0.3);
|
|
824
|
+
}
|
|
825
|
+
#streaming(frames) {
|
|
826
|
+
if (frames === null) return -1;
|
|
827
|
+
const expected = frames.sent + frames.nulled + frames.deficit;
|
|
828
|
+
const passRate = frames.sent / expected;
|
|
829
|
+
const lossRate = frames.nulled / expected;
|
|
830
|
+
const failRate = Math.max(0, frames.deficit) / expected;
|
|
831
|
+
return Math.min(1, passRate - (failRate * 0.7 + lossRate * 0.3));
|
|
832
|
+
}
|
|
833
|
+
#updateMetrics(name, stats) {
|
|
834
|
+
const metrics = this.#cache.get(name);
|
|
835
|
+
if (!metrics) {
|
|
836
|
+
this.#cache.set(name, {
|
|
837
|
+
memory: this.#memory(stats.memory),
|
|
838
|
+
workload: this.#workload(stats.cpu),
|
|
839
|
+
streaming: this.#streaming(stats.frameStats)
|
|
840
|
+
});
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
metrics.memory = this.#memory(stats.memory);
|
|
844
|
+
metrics.workload = this.#workload(stats.cpu);
|
|
845
|
+
metrics.streaming = this.#streaming(stats.frameStats);
|
|
846
|
+
}
|
|
847
|
+
#onConnect = (reconnects, nodeName) => {
|
|
848
|
+
this.#player.emit("nodeConnect", this.#nodes.get(nodeName), reconnects);
|
|
849
|
+
};
|
|
850
|
+
#onReady = (resumed, sessionId, nodeName) => {
|
|
851
|
+
this.#player.emit("nodeReady", this.#nodes.get(nodeName), resumed, sessionId);
|
|
852
|
+
this.fetchInfo(nodeName).catch(noop);
|
|
853
|
+
};
|
|
854
|
+
#onDispatch = (payload, nodeName) => {
|
|
855
|
+
switch (payload.op) {
|
|
856
|
+
case "playerUpdate" /* PlayerUpdate */: {
|
|
857
|
+
this.#player.queues.onStateUpdate(payload);
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
860
|
+
case "stats" /* Stats */: {
|
|
861
|
+
this.#updateMetrics(nodeName, payload);
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
case "event" /* Event */: {
|
|
865
|
+
this.#player.queues.onEventUpdate(payload);
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
this.#player.emit("nodeDispatch", this.#nodes.get(nodeName), payload);
|
|
870
|
+
};
|
|
871
|
+
#onError = (err, nodeName) => {
|
|
872
|
+
this.#player.emit("nodeError", this.#nodes.get(nodeName), err);
|
|
873
|
+
};
|
|
874
|
+
#onClose = (code, reason, nodeName) => {
|
|
875
|
+
this.#cache.delete(nodeName);
|
|
876
|
+
this.#player.emit("nodeClose", this.#nodes.get(nodeName), code, reason);
|
|
877
|
+
if (this.#player.options.relocateQueues) this.#player.queues.relocate(nodeName).catch(noop);
|
|
878
|
+
};
|
|
879
|
+
#onDisconnect = (code, reason, byLocal, nodeName) => {
|
|
880
|
+
this.#cache.delete(nodeName);
|
|
881
|
+
this.#player.voices.regions.forEach((r) => r.forgetNode(nodeName));
|
|
882
|
+
this.#player.emit("nodeDisconnect", this.#nodes.get(nodeName), code, reason, byLocal);
|
|
883
|
+
if (this.#player.options.relocateQueues) this.#player.queues.relocate(nodeName).catch(noop);
|
|
884
|
+
};
|
|
885
|
+
#attachEvents(node) {
|
|
886
|
+
node.on("connect", this.#onConnect);
|
|
887
|
+
node.on("ready", this.#onReady);
|
|
888
|
+
node.on("dispatch", this.#onDispatch);
|
|
889
|
+
node.on("error", this.#onError);
|
|
890
|
+
node.on("close", this.#onClose);
|
|
891
|
+
node.on("disconnect", this.#onDisconnect);
|
|
892
|
+
}
|
|
893
|
+
#detachEvents(node) {
|
|
894
|
+
node.removeListener("connect", this.#onConnect);
|
|
895
|
+
node.removeListener("ready", this.#onReady);
|
|
896
|
+
node.removeListener("dispatch", this.#onDispatch);
|
|
897
|
+
node.removeListener("error", this.#onError);
|
|
898
|
+
node.removeListener("close", this.#onClose);
|
|
899
|
+
node.removeListener("disconnect", this.#onDisconnect);
|
|
900
|
+
}
|
|
901
|
+
[Symbol.iterator]() {
|
|
902
|
+
return this.#nodes[Symbol.iterator]();
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// src/Voice/VoiceState.ts
|
|
907
|
+
var VoiceState = class {
|
|
908
|
+
#cache;
|
|
909
|
+
#player;
|
|
910
|
+
#node;
|
|
911
|
+
#reconnecting = false;
|
|
912
|
+
#changePromise = null;
|
|
913
|
+
constructor(player, node, guildId) {
|
|
914
|
+
const _node = player.nodes.get(node);
|
|
915
|
+
if (!_node) throw new Error(`Node '${node}' not found`);
|
|
916
|
+
if (!_node.ready) throw new Error(`Node '${node}' not ready`);
|
|
917
|
+
const info = player.voices.cache.get(guildId);
|
|
918
|
+
if (!info) throw new Error(`No connection found for guild '${guildId}'`);
|
|
919
|
+
this.#node = _node;
|
|
920
|
+
this.#cache = info;
|
|
921
|
+
this.#player = player;
|
|
922
|
+
}
|
|
923
|
+
get #info() {
|
|
924
|
+
this.#cache = this.#player.voices.cache.get(this.guildId) ?? this.#cache;
|
|
925
|
+
return this.#cache;
|
|
926
|
+
}
|
|
927
|
+
get node() {
|
|
928
|
+
return this.#node;
|
|
929
|
+
}
|
|
930
|
+
get valid() {
|
|
931
|
+
return this.#node.sessionId === this.#info.node_session_id;
|
|
932
|
+
}
|
|
933
|
+
get guildId() {
|
|
934
|
+
return this.#cache.guild_id;
|
|
935
|
+
}
|
|
936
|
+
get channelId() {
|
|
937
|
+
return this.#info.channel_id;
|
|
938
|
+
}
|
|
939
|
+
get regionId() {
|
|
940
|
+
return this.#info.region_id;
|
|
941
|
+
}
|
|
942
|
+
get sessionId() {
|
|
943
|
+
return this.#info.session_id;
|
|
944
|
+
}
|
|
945
|
+
get token() {
|
|
946
|
+
return this.#info.token;
|
|
947
|
+
}
|
|
948
|
+
get endpoint() {
|
|
949
|
+
return this.#info.endpoint;
|
|
950
|
+
}
|
|
951
|
+
get ping() {
|
|
952
|
+
return this.#info.ping;
|
|
953
|
+
}
|
|
954
|
+
get connected() {
|
|
955
|
+
return this.valid && this.#info.connected;
|
|
956
|
+
}
|
|
957
|
+
get destroyed() {
|
|
958
|
+
return this.#player.voices.get(this.guildId) !== this;
|
|
959
|
+
}
|
|
960
|
+
get selfDeaf() {
|
|
961
|
+
return this.#info.self_deaf;
|
|
962
|
+
}
|
|
963
|
+
get selfMute() {
|
|
964
|
+
return this.#info.self_mute;
|
|
965
|
+
}
|
|
966
|
+
get serverDeaf() {
|
|
967
|
+
return this.#info.deaf;
|
|
968
|
+
}
|
|
969
|
+
get serverMute() {
|
|
970
|
+
return this.#info.mute;
|
|
971
|
+
}
|
|
972
|
+
get muted() {
|
|
973
|
+
return this.selfMute || this.#cache.mute || this.#cache.suppress;
|
|
974
|
+
}
|
|
975
|
+
get deafened() {
|
|
976
|
+
return this.selfDeaf || this.#cache.deaf;
|
|
977
|
+
}
|
|
978
|
+
get suppressed() {
|
|
979
|
+
return this.#info.suppress;
|
|
980
|
+
}
|
|
981
|
+
get changingNode() {
|
|
982
|
+
return this.#changePromise !== null;
|
|
983
|
+
}
|
|
984
|
+
get reconnecting() {
|
|
985
|
+
return this.#reconnecting;
|
|
986
|
+
}
|
|
987
|
+
set reconnecting(value) {
|
|
988
|
+
if (typeof value === "boolean") this.#reconnecting = value;
|
|
989
|
+
}
|
|
990
|
+
async destroy(reason) {
|
|
991
|
+
return this.#player.voices.destroy(this.guildId, reason);
|
|
992
|
+
}
|
|
993
|
+
async connect(channelId = this.#info.channel_id) {
|
|
994
|
+
return this.#player.voices.connect(this.guildId, channelId);
|
|
995
|
+
}
|
|
996
|
+
async reconnect() {
|
|
997
|
+
await this.disconnect();
|
|
998
|
+
return this.connect();
|
|
999
|
+
}
|
|
1000
|
+
async disconnect() {
|
|
1001
|
+
return this.#player.voices.disconnect(this.guildId);
|
|
1002
|
+
}
|
|
1003
|
+
async changeNode(name) {
|
|
1004
|
+
const node = this.#player.nodes.get(name);
|
|
1005
|
+
if (!node) throw new Error(`Node '${name}' not found`);
|
|
1006
|
+
if (!node.ready) throw new Error(`Node '${name}' not ready`);
|
|
1007
|
+
if (this.#changePromise !== null) return this.#changePromise;
|
|
1008
|
+
if (name === this.#node.name) throw new Error(`Already on node '${name}'`);
|
|
1009
|
+
const queue = this.#player.queues.get(this.guildId);
|
|
1010
|
+
if (!queue) throw new Error(`No queue found for guild '${this.guildId}'`);
|
|
1011
|
+
const resolver = Promise.withResolvers();
|
|
1012
|
+
this.#changePromise = resolver.promise;
|
|
1013
|
+
const request = {
|
|
1014
|
+
filters: queue.filters.data,
|
|
1015
|
+
paused: queue.paused,
|
|
1016
|
+
voice: { endpoint: this.#info.endpoint, sessionId: this.#cache.session_id, token: this.#cache.token },
|
|
1017
|
+
volume: queue.volume
|
|
1018
|
+
};
|
|
1019
|
+
const wasPlaying = queue.isPlaying && queue.track !== null;
|
|
1020
|
+
if (wasPlaying && this.#player.nodes.info.get(node.name)?.sourceManagers.includes(queue.track.sourceName)) {
|
|
1021
|
+
request.track = { encoded: queue.track.encoded, userData: queue.track.userData };
|
|
1022
|
+
request.position = queue.currentTime;
|
|
1023
|
+
}
|
|
1024
|
+
if (this.valid) await this.#node.rest.destroyPlayer(this.guildId).catch(noop);
|
|
1025
|
+
const previousNode = this.#node;
|
|
1026
|
+
this.#node = node;
|
|
1027
|
+
try {
|
|
1028
|
+
const player = await node.rest.updatePlayer(this.guildId, request);
|
|
1029
|
+
this.#player.queues.cache.set(this.guildId, player);
|
|
1030
|
+
this.#info.node_session_id = node.sessionId;
|
|
1031
|
+
this.#player.emit("voiceChange", this, previousNode, wasPlaying);
|
|
1032
|
+
resolver.resolve();
|
|
1033
|
+
} catch (err) {
|
|
1034
|
+
resolver.reject(err);
|
|
1035
|
+
throw err;
|
|
1036
|
+
} finally {
|
|
1037
|
+
this.#changePromise = null;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// src/Voice/VoiceRegion.ts
|
|
1043
|
+
var VoiceRegion = class {
|
|
1044
|
+
#player;
|
|
1045
|
+
#records = /* @__PURE__ */ new Map();
|
|
1046
|
+
id;
|
|
1047
|
+
constructor(player, regionId) {
|
|
1048
|
+
this.#player = player;
|
|
1049
|
+
this.id = regionId;
|
|
1050
|
+
Object.defineProperty(this, "id", {
|
|
1051
|
+
writable: false,
|
|
1052
|
+
configurable: false
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
get nodes() {
|
|
1056
|
+
return this.#records.keys().toArray();
|
|
1057
|
+
}
|
|
1058
|
+
forgetNode(name) {
|
|
1059
|
+
this.#records.delete(name);
|
|
1060
|
+
}
|
|
1061
|
+
onPingUpdate(name, ping, time) {
|
|
1062
|
+
if (!this.#player.nodes.state(name, "ready")) return;
|
|
1063
|
+
if (ping <= 0 || time <= 0) return;
|
|
1064
|
+
const node = this.#records.get(name);
|
|
1065
|
+
if (!node) {
|
|
1066
|
+
this.#records.set(name, { pings: [ping], lastPingTime: time });
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (time - node.lastPingTime < 12e3) return;
|
|
1070
|
+
node.lastPingTime = time;
|
|
1071
|
+
node.pings.push(ping);
|
|
1072
|
+
if (node.pings.length > 5) node.pings.shift();
|
|
1073
|
+
}
|
|
1074
|
+
getAveragePing(name) {
|
|
1075
|
+
const pings = this.#records.get(name)?.pings;
|
|
1076
|
+
return !pings?.length ? 0 : pings.reduce((t, c) => t + c, 0) / pings.length;
|
|
1077
|
+
}
|
|
1078
|
+
getRelevantNode(...exclusions) {
|
|
1079
|
+
return this.#player.nodes.relevant().filter((n) => !exclusions.includes(n.name)).sort((a, b) => {
|
|
1080
|
+
if (!this.#records.has(a.name)) return -1;
|
|
1081
|
+
if (!this.#records.has(b.name)) return 1;
|
|
1082
|
+
return this.getAveragePing(a.name) - this.getAveragePing(b.name);
|
|
1083
|
+
})[0];
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
var VoiceManager = class {
|
|
1087
|
+
#player;
|
|
1088
|
+
#voices = /* @__PURE__ */ new Map();
|
|
1089
|
+
#joins = /* @__PURE__ */ new Map();
|
|
1090
|
+
#destroys = /* @__PURE__ */ new Map();
|
|
1091
|
+
#cache = /* @__PURE__ */ new Map();
|
|
1092
|
+
regions = /* @__PURE__ */ new Map();
|
|
1093
|
+
constructor(player) {
|
|
1094
|
+
this.#player = player;
|
|
1095
|
+
Object.defineProperty(this, "regions", {
|
|
1096
|
+
writable: false,
|
|
1097
|
+
configurable: false
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
get size() {
|
|
1101
|
+
return this.#voices.size;
|
|
1102
|
+
}
|
|
1103
|
+
get cache() {
|
|
1104
|
+
return this.#cache;
|
|
1105
|
+
}
|
|
1106
|
+
get(guildId) {
|
|
1107
|
+
return this.#voices.get(guildId);
|
|
1108
|
+
}
|
|
1109
|
+
has(guildId) {
|
|
1110
|
+
return this.#voices.has(guildId);
|
|
1111
|
+
}
|
|
1112
|
+
keys() {
|
|
1113
|
+
return this.#voices.keys();
|
|
1114
|
+
}
|
|
1115
|
+
values() {
|
|
1116
|
+
return this.#voices.values();
|
|
1117
|
+
}
|
|
1118
|
+
entries() {
|
|
1119
|
+
return this.#voices.entries();
|
|
1120
|
+
}
|
|
1121
|
+
async destroy(guildId, reason = "destroyed") {
|
|
1122
|
+
if (this.#player.queues.has(guildId)) return this.#player.queues.destroy(guildId, reason);
|
|
1123
|
+
if (this.#destroys.has(guildId)) return this.#destroys.get(guildId);
|
|
1124
|
+
const voice = this.#voices.get(guildId);
|
|
1125
|
+
if (!voice) return;
|
|
1126
|
+
const resolver = Promise.withResolvers();
|
|
1127
|
+
this.#destroys.set(guildId, resolver.promise);
|
|
1128
|
+
await voice.disconnect().catch(noop);
|
|
1129
|
+
this.#voices.delete(guildId);
|
|
1130
|
+
this.#player.emit("voiceDestroy", voice, reason);
|
|
1131
|
+
this.#destroys.delete(guildId);
|
|
1132
|
+
resolver.resolve();
|
|
1133
|
+
}
|
|
1134
|
+
async connect(guildId, voiceId, options) {
|
|
1135
|
+
if (!isString(guildId, SnowflakeRegex)) throw new Error("Guild Id is not a valid Discord Id");
|
|
1136
|
+
if (!isString(voiceId, SnowflakeRegex)) throw new Error("Voice Id is not a valid Discord Id");
|
|
1137
|
+
if (this.#joins.has(guildId)) {
|
|
1138
|
+
const request2 = this.#joins.get(guildId);
|
|
1139
|
+
if (request2.voiceId === voiceId) return request2.promise;
|
|
1140
|
+
throw new Error("Another connection to the same guild is in progress");
|
|
1141
|
+
}
|
|
1142
|
+
const voice = this.#voices.get(guildId);
|
|
1143
|
+
const joined = voice?.channelId === voiceId;
|
|
1144
|
+
if (joined && voice.connected && this.#cache.has(guildId)) return voice;
|
|
1145
|
+
if (options?.node !== void 0 && !this.#player.nodes.state(options.node, "ready")) {
|
|
1146
|
+
throw new Error(`Node '${options.node}' not ready`);
|
|
1147
|
+
}
|
|
1148
|
+
const request = Promise.withResolvers();
|
|
1149
|
+
request.voiceId = voiceId;
|
|
1150
|
+
if (options?.node !== void 0) request.node = options.node;
|
|
1151
|
+
if (options?.context !== void 0) request.context = options.context;
|
|
1152
|
+
if (options?.filters !== void 0) request.config = { filters: options.filters };
|
|
1153
|
+
if (options?.volume !== void 0) {
|
|
1154
|
+
request.config ??= {};
|
|
1155
|
+
request.config.volume = options.volume;
|
|
1156
|
+
}
|
|
1157
|
+
this.#joins.set(guildId, request);
|
|
1158
|
+
if (joined) {
|
|
1159
|
+
voice.reconnecting = true;
|
|
1160
|
+
await this.#sendVoiceUpdate(guildId, null);
|
|
1161
|
+
}
|
|
1162
|
+
await this.#sendVoiceUpdate(guildId, voiceId);
|
|
1163
|
+
const controller = new AbortController();
|
|
1164
|
+
try {
|
|
1165
|
+
const state = await Promise.race([request.promise, setTimeout$1(3e4, null, { signal: controller.signal })]);
|
|
1166
|
+
if (state === null) throw new Error(`Connection timed out - Guild[${guildId}] Voice[${voiceId}]`);
|
|
1167
|
+
return state;
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
this.#cache.delete(guildId);
|
|
1170
|
+
await this.#sendVoiceUpdate(guildId, null);
|
|
1171
|
+
request.reject(err);
|
|
1172
|
+
throw err;
|
|
1173
|
+
} finally {
|
|
1174
|
+
controller.abort();
|
|
1175
|
+
if (voice?.reconnecting) voice.reconnecting = false;
|
|
1176
|
+
this.#joins.delete(guildId);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
async disconnect(guildId) {
|
|
1180
|
+
if (!this.#voices.has(guildId)) throw new Error(`No connection found for guild '${guildId}'`);
|
|
1181
|
+
this.#cache.delete(guildId);
|
|
1182
|
+
return this.#sendVoiceUpdate(guildId, null);
|
|
1183
|
+
}
|
|
1184
|
+
#getVoiceRegion(id) {
|
|
1185
|
+
if (this.regions.has(id)) return this.regions.get(id);
|
|
1186
|
+
const region = new VoiceRegion(this.#player, id);
|
|
1187
|
+
this.regions.set(id, region);
|
|
1188
|
+
return region;
|
|
1189
|
+
}
|
|
1190
|
+
async #sendVoiceUpdate(guildId, channelId) {
|
|
1191
|
+
return this.#player.options.forwardVoiceUpdate(guildId, {
|
|
1192
|
+
op: 4,
|
|
1193
|
+
d: {
|
|
1194
|
+
guild_id: guildId,
|
|
1195
|
+
channel_id: channelId,
|
|
1196
|
+
self_deaf: channelId !== null,
|
|
1197
|
+
self_mute: false
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
handleDispatch(payload) {
|
|
1202
|
+
if (payload.op !== 0) return;
|
|
1203
|
+
switch (payload.t) {
|
|
1204
|
+
case "VOICE_STATE_UPDATE":
|
|
1205
|
+
this.#onStateUpdate(payload.d);
|
|
1206
|
+
return;
|
|
1207
|
+
case "VOICE_SERVER_UPDATE":
|
|
1208
|
+
this.#onServerUpdate(payload.d);
|
|
1209
|
+
return;
|
|
1210
|
+
case "READY":
|
|
1211
|
+
this.#onClientReady(payload.d);
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
async #onClientReady(data) {
|
|
1216
|
+
if (this.#player.initialized) return;
|
|
1217
|
+
return this.#player.init(data.user.id);
|
|
1218
|
+
}
|
|
1219
|
+
#onStateUpdate(data) {
|
|
1220
|
+
if (!data.guild_id || data.user_id !== this.#player.clientId) return;
|
|
1221
|
+
if (data.channel_id === null) {
|
|
1222
|
+
this.#cache.delete(data.guild_id);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
if (!this.#voices.has(data.guild_id) && !this.#joins.has(data.guild_id)) return;
|
|
1226
|
+
const state = this.#cache.get(data.guild_id);
|
|
1227
|
+
if (!state) {
|
|
1228
|
+
this.#cache.set(data.guild_id, {
|
|
1229
|
+
channel_id: data.channel_id,
|
|
1230
|
+
connected: false,
|
|
1231
|
+
deaf: data.deaf,
|
|
1232
|
+
endpoint: "",
|
|
1233
|
+
guild_id: data.guild_id,
|
|
1234
|
+
mute: data.mute,
|
|
1235
|
+
node_session_id: "",
|
|
1236
|
+
ping: -1,
|
|
1237
|
+
region_id: "unknown",
|
|
1238
|
+
self_deaf: data.self_deaf,
|
|
1239
|
+
self_mute: data.self_mute,
|
|
1240
|
+
session_id: data.session_id,
|
|
1241
|
+
suppress: data.suppress,
|
|
1242
|
+
token: ""
|
|
1243
|
+
});
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
state.channel_id = data.channel_id;
|
|
1247
|
+
state.deaf = data.deaf;
|
|
1248
|
+
state.mute = data.mute;
|
|
1249
|
+
state.self_deaf = data.self_deaf;
|
|
1250
|
+
state.self_mute = data.self_mute;
|
|
1251
|
+
state.session_id = data.session_id;
|
|
1252
|
+
state.suppress = data.suppress;
|
|
1253
|
+
}
|
|
1254
|
+
async #onServerUpdate(data) {
|
|
1255
|
+
if (data.endpoint === null) return;
|
|
1256
|
+
const state = this.#cache.get(data.guild_id);
|
|
1257
|
+
const request = this.#joins.get(data.guild_id);
|
|
1258
|
+
if (!state) return request?.reject(new Error("No voice state received"));
|
|
1259
|
+
state.token = data.token;
|
|
1260
|
+
state.endpoint = data.endpoint;
|
|
1261
|
+
state.region_id = data.endpoint.match(VoiceRegionIdRegex)?.[0] ?? "unknown";
|
|
1262
|
+
const region = this.#getVoiceRegion(state.region_id);
|
|
1263
|
+
const reqNode = request?.node !== void 0;
|
|
1264
|
+
let voice = this.#voices.get(data.guild_id);
|
|
1265
|
+
const node = voice?.node ?? (reqNode ? this.#player.nodes.get(request.node) : region.getRelevantNode());
|
|
1266
|
+
if (!node?.ready)
|
|
1267
|
+
return request?.reject(new Error(reqNode ? `Node '${request.node}' unavailable` : "No nodes available"));
|
|
1268
|
+
try {
|
|
1269
|
+
const player = await node.rest.updatePlayer(data.guild_id, {
|
|
1270
|
+
...request?.config,
|
|
1271
|
+
voice: {
|
|
1272
|
+
endpoint: data.endpoint,
|
|
1273
|
+
sessionId: state.session_id,
|
|
1274
|
+
token: data.token
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
this.#player.queues.cache.set(data.guild_id, player);
|
|
1278
|
+
state.node_session_id = node.sessionId;
|
|
1279
|
+
state.connected = player.state.connected;
|
|
1280
|
+
state.ping = player.state.ping;
|
|
1281
|
+
if (!voice) {
|
|
1282
|
+
voice = new VoiceState(this.#player, node.name, data.guild_id);
|
|
1283
|
+
this.#voices.set(data.guild_id, voice);
|
|
1284
|
+
}
|
|
1285
|
+
this.#player.emit("voiceConnect", voice);
|
|
1286
|
+
if (!this.#player.queues.has(data.guild_id)) {
|
|
1287
|
+
const options = { guildId: data.guild_id, voiceId: state.channel_id };
|
|
1288
|
+
if (request?.context !== void 0) options.context = request.context;
|
|
1289
|
+
await this.#player.queues.create(options);
|
|
1290
|
+
}
|
|
1291
|
+
request?.resolve(voice);
|
|
1292
|
+
} catch (err) {
|
|
1293
|
+
request?.reject(err);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
[Symbol.iterator]() {
|
|
1297
|
+
return this.#voices[Symbol.iterator]();
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
// src/Queue/Track.ts
|
|
1302
|
+
var Track = class {
|
|
1303
|
+
id;
|
|
1304
|
+
title = "Unknown Track";
|
|
1305
|
+
author = "Unknown Author";
|
|
1306
|
+
isLive = false;
|
|
1307
|
+
isSeekable = false;
|
|
1308
|
+
duration = 0;
|
|
1309
|
+
formattedDuration = "00:00";
|
|
1310
|
+
uri = null;
|
|
1311
|
+
isrc = null;
|
|
1312
|
+
url = null;
|
|
1313
|
+
artworkUrl = null;
|
|
1314
|
+
userData = {};
|
|
1315
|
+
pluginInfo = {};
|
|
1316
|
+
encoded;
|
|
1317
|
+
sourceName = "unknown";
|
|
1318
|
+
constructor(data) {
|
|
1319
|
+
if (!isRecord(data)) throw new Error("Track data must be an object");
|
|
1320
|
+
if (!isRecord(data.info)) throw new Error("Track info is not an object");
|
|
1321
|
+
if (isString(data.info.identifier, "non-empty")) this.id = data.info.identifier;
|
|
1322
|
+
else throw new Error("Track does not have an identifier");
|
|
1323
|
+
if (isString(data.encoded, "non-empty")) this.encoded = data.encoded;
|
|
1324
|
+
else throw new Error("Track does not have an encoded data string");
|
|
1325
|
+
if (isString(data.info.title, "non-empty")) this.title = data.info.title;
|
|
1326
|
+
if (isString(data.info.author, "non-empty")) this.author = data.info.author;
|
|
1327
|
+
if (data.info.isStream) this.isLive = true;
|
|
1328
|
+
if (data.info.isSeekable) this.isSeekable = true;
|
|
1329
|
+
if (this.isLive) {
|
|
1330
|
+
this.duration = Number.POSITIVE_INFINITY;
|
|
1331
|
+
this.formattedDuration = "Live";
|
|
1332
|
+
} else if (isNumber(data.info.length, "natural")) {
|
|
1333
|
+
this.duration = data.info.length;
|
|
1334
|
+
this.formattedDuration = formatDuration(this.duration);
|
|
1335
|
+
}
|
|
1336
|
+
if (isString(data.info.uri, "non-empty")) this.uri = data.info.uri;
|
|
1337
|
+
if (isString(data.info.isrc, "non-empty")) this.isrc = data.info.isrc;
|
|
1338
|
+
if (isString(this.uri, "url")) this.url = this.uri;
|
|
1339
|
+
if (isString(data.info.artworkUrl, "url")) this.artworkUrl = data.info.artworkUrl;
|
|
1340
|
+
if (isRecord(data.userData, "non-empty")) this.userData = data.userData;
|
|
1341
|
+
if (isRecord(data.pluginInfo, "non-empty")) this.pluginInfo = data.pluginInfo;
|
|
1342
|
+
if (isString(data.info.sourceName, "non-empty")) this.sourceName = data.info.sourceName;
|
|
1343
|
+
}
|
|
1344
|
+
toString() {
|
|
1345
|
+
return this.title;
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
// src/Queue/Playlist.ts
|
|
1350
|
+
var Playlist = class {
|
|
1351
|
+
name = "Unknown Playlist";
|
|
1352
|
+
selectedTrack = -1;
|
|
1353
|
+
tracks = [];
|
|
1354
|
+
pluginInfo = {};
|
|
1355
|
+
duration = 0;
|
|
1356
|
+
formattedDuration = "00:00";
|
|
1357
|
+
constructor(data) {
|
|
1358
|
+
if (!isRecord(data)) throw new Error("Playlist data must be an object");
|
|
1359
|
+
if (!isRecord(data.info)) throw new Error("Playlist info is not an object");
|
|
1360
|
+
if (isString(data.info.name, "non-empty")) this.name = data.info.name;
|
|
1361
|
+
if (isNumber(data.info.selectedTrack, "whole")) this.selectedTrack = data.info.selectedTrack;
|
|
1362
|
+
for (let i = 0, track; i < data.tracks.length; i++) {
|
|
1363
|
+
track = new Track(data.tracks[i]);
|
|
1364
|
+
if (!track.isLive) this.duration += track.duration;
|
|
1365
|
+
this.tracks.push(track);
|
|
1366
|
+
}
|
|
1367
|
+
if (isRecord(data.pluginInfo, "non-empty")) this.pluginInfo = data.pluginInfo;
|
|
1368
|
+
if (this.duration > 0) this.formattedDuration = formatDuration(this.duration);
|
|
1369
|
+
}
|
|
1370
|
+
toString() {
|
|
1371
|
+
return this.name;
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1375
|
+
// src/Queue/FilterManager.ts
|
|
1376
|
+
var FilterManager = class {
|
|
1377
|
+
#manager;
|
|
1378
|
+
#cache;
|
|
1379
|
+
#voice;
|
|
1380
|
+
constructor(player, guildId) {
|
|
1381
|
+
const cache = player.queues.cache.get(guildId);
|
|
1382
|
+
if (!cache) throw new Error(`No player found for guild '${guildId}'`);
|
|
1383
|
+
const voice = player.voices.get(guildId);
|
|
1384
|
+
if (!voice) throw new Error(`No connection found for guild '${guildId}'`);
|
|
1385
|
+
this.#manager = player.queues;
|
|
1386
|
+
this.#cache = cache;
|
|
1387
|
+
this.#voice = voice;
|
|
1388
|
+
}
|
|
1389
|
+
get #data() {
|
|
1390
|
+
this.#cache = this.#manager.cache.get(this.#voice.guildId) ?? this.#cache;
|
|
1391
|
+
return this.#cache;
|
|
1392
|
+
}
|
|
1393
|
+
set #data(data) {
|
|
1394
|
+
this.#manager.cache.set(this.#voice.guildId, data);
|
|
1395
|
+
this.#cache = data;
|
|
1396
|
+
}
|
|
1397
|
+
get data() {
|
|
1398
|
+
return this.#data.filters;
|
|
1399
|
+
}
|
|
1400
|
+
get(name) {
|
|
1401
|
+
return this.#data.filters[name] ?? this.#cache.filters.pluginFilters?.[name] ?? null;
|
|
1402
|
+
}
|
|
1403
|
+
async set(name, value, isPlugin = false) {
|
|
1404
|
+
if (name === "pluginFilters") return null;
|
|
1405
|
+
if (!isPlugin) this.#data.filters[name] = value;
|
|
1406
|
+
else {
|
|
1407
|
+
this.#cache.filters.pluginFilters ??= {};
|
|
1408
|
+
this.#cache.filters.pluginFilters[name] = value;
|
|
1409
|
+
}
|
|
1410
|
+
await this.override(this.#cache.filters);
|
|
1411
|
+
return (isPlugin ? this.#cache.filters.pluginFilters?.[name] : this.#cache.filters[name]) ?? null;
|
|
1412
|
+
}
|
|
1413
|
+
has(name) {
|
|
1414
|
+
if (name === "pluginFilters") return false;
|
|
1415
|
+
return name in this.#data.filters || this.#cache.filters.pluginFilters !== void 0 && name in this.#cache.filters.pluginFilters;
|
|
1416
|
+
}
|
|
1417
|
+
async merge(filters) {
|
|
1418
|
+
return this.override({ ...this.#data.filters, ...filters });
|
|
1419
|
+
}
|
|
1420
|
+
async remove(...names) {
|
|
1421
|
+
if (names.length === 0) return this.#data.filters;
|
|
1422
|
+
for (const filter in this.#data.filters) {
|
|
1423
|
+
if (names.includes(filter)) delete this.#cache.filters[filter];
|
|
1424
|
+
}
|
|
1425
|
+
for (const filter in this.#cache.filters.pluginFilters) {
|
|
1426
|
+
if (names.includes(filter)) delete this.#cache.filters.pluginFilters?.[filter];
|
|
1427
|
+
}
|
|
1428
|
+
return this.override(this.#cache.filters);
|
|
1429
|
+
}
|
|
1430
|
+
async clear() {
|
|
1431
|
+
return this.override({});
|
|
1432
|
+
}
|
|
1433
|
+
async override(filters) {
|
|
1434
|
+
this.#data = await this.#voice.node.rest.updatePlayer(this.#voice.guildId, { filters });
|
|
1435
|
+
return this.#cache.filters;
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
// src/Queue/Queue.ts
|
|
1440
|
+
var Queue = class {
|
|
1441
|
+
#cache;
|
|
1442
|
+
#player;
|
|
1443
|
+
#autoplay = false;
|
|
1444
|
+
#repeatMode = "none";
|
|
1445
|
+
#tracks = [];
|
|
1446
|
+
#previousTracks = [];
|
|
1447
|
+
context = {};
|
|
1448
|
+
voice;
|
|
1449
|
+
filters;
|
|
1450
|
+
constructor(player, guildId, context) {
|
|
1451
|
+
if (player.voices.has(guildId)) {
|
|
1452
|
+
this.voice = player.voices.get(guildId);
|
|
1453
|
+
} else {
|
|
1454
|
+
throw new Error(`No connection found for guild '${guildId}'`);
|
|
1455
|
+
}
|
|
1456
|
+
if (player.queues.cache.has(guildId)) {
|
|
1457
|
+
this.#cache = player.queues.cache.get(guildId);
|
|
1458
|
+
} else {
|
|
1459
|
+
throw new Error(`No player found for guild '${guildId}'`);
|
|
1460
|
+
}
|
|
1461
|
+
this.#player = player;
|
|
1462
|
+
this.filters = new FilterManager(player, guildId);
|
|
1463
|
+
if (context !== void 0) this.context = context;
|
|
1464
|
+
const immutable = {
|
|
1465
|
+
writable: false,
|
|
1466
|
+
configurable: false
|
|
1467
|
+
};
|
|
1468
|
+
Object.defineProperties(this, {
|
|
1469
|
+
voice: immutable,
|
|
1470
|
+
filters: immutable
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
get #data() {
|
|
1474
|
+
this.#cache = this.#player.queues.cache.get(this.guildId) ?? this.#cache;
|
|
1475
|
+
return this.#cache;
|
|
1476
|
+
}
|
|
1477
|
+
set #data(data) {
|
|
1478
|
+
this.#player.queues.cache.set(this.guildId, data);
|
|
1479
|
+
this.#cache = data;
|
|
1480
|
+
}
|
|
1481
|
+
get node() {
|
|
1482
|
+
return this.voice.node;
|
|
1483
|
+
}
|
|
1484
|
+
get rest() {
|
|
1485
|
+
return this.voice.node.rest;
|
|
1486
|
+
}
|
|
1487
|
+
get guildId() {
|
|
1488
|
+
return this.voice.guildId;
|
|
1489
|
+
}
|
|
1490
|
+
get volume() {
|
|
1491
|
+
return this.#data.volume;
|
|
1492
|
+
}
|
|
1493
|
+
get paused() {
|
|
1494
|
+
return this.#data.paused;
|
|
1495
|
+
}
|
|
1496
|
+
get stopped() {
|
|
1497
|
+
return this.track !== null && this.#data.track === null;
|
|
1498
|
+
}
|
|
1499
|
+
get autoplay() {
|
|
1500
|
+
return this.#autoplay;
|
|
1501
|
+
}
|
|
1502
|
+
get finished() {
|
|
1503
|
+
return this.#tracks.length === 0;
|
|
1504
|
+
}
|
|
1505
|
+
get destroyed() {
|
|
1506
|
+
return this.#player.queues.get(this.guildId) !== this;
|
|
1507
|
+
}
|
|
1508
|
+
get repeatMode() {
|
|
1509
|
+
return this.#repeatMode;
|
|
1510
|
+
}
|
|
1511
|
+
get isEmpty() {
|
|
1512
|
+
return this.finished && !this.hasPrevious;
|
|
1513
|
+
}
|
|
1514
|
+
get isPlaying() {
|
|
1515
|
+
return !this.paused && this.#data.track !== null;
|
|
1516
|
+
}
|
|
1517
|
+
get hasNext() {
|
|
1518
|
+
return this.#tracks.length > 1;
|
|
1519
|
+
}
|
|
1520
|
+
get hasPrevious() {
|
|
1521
|
+
return this.#previousTracks.length !== 0;
|
|
1522
|
+
}
|
|
1523
|
+
get track() {
|
|
1524
|
+
return this.#tracks[0] ?? null;
|
|
1525
|
+
}
|
|
1526
|
+
get previousTrack() {
|
|
1527
|
+
return this.#previousTracks[this.#previousTracks.length - 1] ?? null;
|
|
1528
|
+
}
|
|
1529
|
+
get tracks() {
|
|
1530
|
+
return this.#tracks;
|
|
1531
|
+
}
|
|
1532
|
+
get previousTracks() {
|
|
1533
|
+
return this.#previousTracks;
|
|
1534
|
+
}
|
|
1535
|
+
get length() {
|
|
1536
|
+
return this.#tracks.length;
|
|
1537
|
+
}
|
|
1538
|
+
get totalLength() {
|
|
1539
|
+
return this.length + this.#previousTracks.length;
|
|
1540
|
+
}
|
|
1541
|
+
get duration() {
|
|
1542
|
+
return this.#tracks.reduce((time, track) => time + (track.isLive ? 0 : track.duration), 0);
|
|
1543
|
+
}
|
|
1544
|
+
get formattedDuration() {
|
|
1545
|
+
return formatDuration(this.duration);
|
|
1546
|
+
}
|
|
1547
|
+
get currentTime() {
|
|
1548
|
+
if (this.#data.paused) return this.#cache.state.position;
|
|
1549
|
+
if (this.#cache.state.position === 0) return 0;
|
|
1550
|
+
return this.#cache.state.position + (Date.now() - this.#cache.state.time);
|
|
1551
|
+
}
|
|
1552
|
+
get formattedCurrentTime() {
|
|
1553
|
+
return formatDuration(this.currentTime);
|
|
1554
|
+
}
|
|
1555
|
+
#error(data) {
|
|
1556
|
+
const explicit = typeof data === "string";
|
|
1557
|
+
const message = explicit ? data : data.message ?? data.cause;
|
|
1558
|
+
const error = new Error(message);
|
|
1559
|
+
error.name = `Error [${this.constructor.name}]`;
|
|
1560
|
+
error.cause = message;
|
|
1561
|
+
error.severity = explicit ? "common" /* Common */ : data.severity;
|
|
1562
|
+
return error;
|
|
1563
|
+
}
|
|
1564
|
+
async search(query, prefix = this.#player.options.queryPrefix) {
|
|
1565
|
+
return this.#player.search(query, { prefix, node: this.node.name });
|
|
1566
|
+
}
|
|
1567
|
+
add(source, userData) {
|
|
1568
|
+
if (source instanceof Track) {
|
|
1569
|
+
Object.assign(source.userData, userData);
|
|
1570
|
+
this.#tracks.push(source);
|
|
1571
|
+
} else if (source instanceof Playlist) {
|
|
1572
|
+
for (const track of source.tracks) {
|
|
1573
|
+
Object.assign(track.userData, userData);
|
|
1574
|
+
this.#tracks.push(track);
|
|
1575
|
+
}
|
|
1576
|
+
} else if (Array.isArray(source) && source.every((t) => t instanceof Track)) {
|
|
1577
|
+
for (const track of source) {
|
|
1578
|
+
Object.assign(track.userData, userData);
|
|
1579
|
+
this.#tracks.push(track);
|
|
1580
|
+
}
|
|
1581
|
+
} else throw new Error("Source must be a track, playlist, or array of tracks");
|
|
1582
|
+
return this;
|
|
1583
|
+
}
|
|
1584
|
+
async addRelated(refTrack) {
|
|
1585
|
+
refTrack ??= this.track ?? this.previousTrack;
|
|
1586
|
+
if (!refTrack) throw new Error("The queue is empty and there is no track to refer");
|
|
1587
|
+
const relatedTracks = await this.#player.options.fetchRelatedTracks(this, refTrack);
|
|
1588
|
+
this.add(relatedTracks);
|
|
1589
|
+
return relatedTracks;
|
|
1590
|
+
}
|
|
1591
|
+
remove(input) {
|
|
1592
|
+
if (isNumber(input, "integer")) {
|
|
1593
|
+
if (input === 0) return;
|
|
1594
|
+
if (input < 0) return this.#previousTracks.splice(input, 1)[0];
|
|
1595
|
+
return this.#tracks.splice(input, 1)[0];
|
|
1596
|
+
}
|
|
1597
|
+
if (Array.isArray(input) && input.every((i) => isNumber(i, "integer"))) {
|
|
1598
|
+
if (input.length === 0) return [];
|
|
1599
|
+
const tracks = [];
|
|
1600
|
+
for (let indices = input.toSorted((a, b) => a - b), index = indices[0], deletions = 0, i = 0; i < indices.length; index = indices[++i] - deletions) {
|
|
1601
|
+
if (index === 0) continue;
|
|
1602
|
+
if (index < 0) tracks.push(...this.#previousTracks.splice(index, 1));
|
|
1603
|
+
else if (index < this.#tracks.length) tracks.push(...this.#tracks.splice(index, 1)), deletions++;
|
|
1604
|
+
}
|
|
1605
|
+
return tracks;
|
|
1606
|
+
}
|
|
1607
|
+
throw new Error("Input must be a index or array of indices");
|
|
1608
|
+
}
|
|
1609
|
+
async jump(index) {
|
|
1610
|
+
if (this.isEmpty) throw this.#error("The queue is empty at the moment");
|
|
1611
|
+
if (!isNumber(index, "integer")) throw this.#error("Index must be a integer");
|
|
1612
|
+
const track = index < 0 ? this.#previousTracks[this.#previousTracks.length + index] : this.#tracks[index];
|
|
1613
|
+
if (!track) throw this.#error("Specified index is out of range");
|
|
1614
|
+
if (index < 0) this.#tracks.unshift(...this.#previousTracks.splice(index));
|
|
1615
|
+
else this.#previousTracks.push(...this.#tracks.splice(0, index));
|
|
1616
|
+
this.#data = await this.rest.updatePlayer(this.guildId, {
|
|
1617
|
+
paused: false,
|
|
1618
|
+
track: { encoded: track.encoded, userData: track.userData }
|
|
1619
|
+
});
|
|
1620
|
+
return track;
|
|
1621
|
+
}
|
|
1622
|
+
async pause() {
|
|
1623
|
+
this.#data = await this.rest.updatePlayer(this.guildId, { paused: true });
|
|
1624
|
+
return this.#cache.paused;
|
|
1625
|
+
}
|
|
1626
|
+
async resume() {
|
|
1627
|
+
if (this.stopped) await this.jump(0);
|
|
1628
|
+
else this.#data = await this.rest.updatePlayer(this.guildId, { paused: false });
|
|
1629
|
+
return !this.#cache.paused;
|
|
1630
|
+
}
|
|
1631
|
+
async seek(ms) {
|
|
1632
|
+
if (this.track === null) throw this.#error("No track's playing at the moment");
|
|
1633
|
+
if (!this.track.isSeekable) throw this.#error("Current track is not seekable");
|
|
1634
|
+
if (!isNumber(ms, "whole")) throw this.#error("Seek time must be a whole number");
|
|
1635
|
+
if (ms > this.track.duration) throw this.#error("Specified time to seek is out of range");
|
|
1636
|
+
const _body = { paused: false, position: ms };
|
|
1637
|
+
if (this.#data.track?.info.identifier !== this.track.id) {
|
|
1638
|
+
_body.track = { encoded: this.track.encoded, userData: this.track.userData };
|
|
1639
|
+
}
|
|
1640
|
+
this.#data = await this.rest.updatePlayer(this.guildId, _body);
|
|
1641
|
+
return this.#cache.state.position;
|
|
1642
|
+
}
|
|
1643
|
+
async next() {
|
|
1644
|
+
if (this.hasNext) return this.jump(1);
|
|
1645
|
+
if (this.hasPrevious && this.#repeatMode === "queue") {
|
|
1646
|
+
this.#tracks.push(this.#previousTracks.shift());
|
|
1647
|
+
return this.jump(this.hasNext ? 1 : 0);
|
|
1648
|
+
}
|
|
1649
|
+
if (this.#autoplay) {
|
|
1650
|
+
const related = await this.addRelated();
|
|
1651
|
+
if (related.length > 0) return this.jump(this.length - related.length);
|
|
1652
|
+
}
|
|
1653
|
+
if (!this.finished) {
|
|
1654
|
+
this.#previousTracks.push(this.#tracks.shift());
|
|
1655
|
+
await this.stop();
|
|
1656
|
+
}
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
async previous() {
|
|
1660
|
+
if (this.hasPrevious) return this.jump(-1);
|
|
1661
|
+
return null;
|
|
1662
|
+
}
|
|
1663
|
+
shuffle(includePrevious = false) {
|
|
1664
|
+
if (includePrevious === true) this.#tracks.push(...this.#previousTracks.splice(0));
|
|
1665
|
+
if (this.#tracks.length < 3) return this;
|
|
1666
|
+
for (let arr = this.#tracks, i = arr.length - 1, j; i > 1; --i) {
|
|
1667
|
+
j = Math.floor(Math.random() * i) + 1;
|
|
1668
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
1669
|
+
}
|
|
1670
|
+
return this;
|
|
1671
|
+
}
|
|
1672
|
+
async setVolume(volume) {
|
|
1673
|
+
if (!isNumber(volume, "whole")) throw this.#error("Volume must be a whole number");
|
|
1674
|
+
if (volume > 1e3) throw this.#error("Volume cannot be more than 1000");
|
|
1675
|
+
this.#data = await this.rest.updatePlayer(this.guildId, { volume });
|
|
1676
|
+
return this.#cache.volume;
|
|
1677
|
+
}
|
|
1678
|
+
setAutoplay(autoplay = false) {
|
|
1679
|
+
if (typeof autoplay === "boolean") this.#autoplay = autoplay;
|
|
1680
|
+
else throw this.#error("Autoplay must be a boolean value");
|
|
1681
|
+
return this.#autoplay;
|
|
1682
|
+
}
|
|
1683
|
+
setRepeatMode(repeatMode = "none") {
|
|
1684
|
+
if (repeatMode === "track" || repeatMode === "queue" || repeatMode === "none") this.#repeatMode = repeatMode;
|
|
1685
|
+
else throw this.#error("Repeat mode can only be set to track, queue, or none");
|
|
1686
|
+
return this.#repeatMode;
|
|
1687
|
+
}
|
|
1688
|
+
async stop() {
|
|
1689
|
+
this.#data = await this.rest.updatePlayer(this.guildId, { track: { encoded: null } });
|
|
1690
|
+
}
|
|
1691
|
+
async destroy(reason) {
|
|
1692
|
+
return this.#player.queues.destroy(this.guildId, reason);
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
// src/Queue/QueueManager.ts
|
|
1697
|
+
var QueueManager = class {
|
|
1698
|
+
#player;
|
|
1699
|
+
#queues = /* @__PURE__ */ new Map();
|
|
1700
|
+
#cache = /* @__PURE__ */ new Map();
|
|
1701
|
+
#destroys = /* @__PURE__ */ new Map();
|
|
1702
|
+
#relocations = /* @__PURE__ */ new Map();
|
|
1703
|
+
constructor(player) {
|
|
1704
|
+
this.#player = player;
|
|
1705
|
+
}
|
|
1706
|
+
get size() {
|
|
1707
|
+
return this.#queues.size;
|
|
1708
|
+
}
|
|
1709
|
+
get cache() {
|
|
1710
|
+
return this.#cache;
|
|
1711
|
+
}
|
|
1712
|
+
get(guildId) {
|
|
1713
|
+
return this.#queues.get(guildId);
|
|
1714
|
+
}
|
|
1715
|
+
has(guildId) {
|
|
1716
|
+
return this.#queues.has(guildId);
|
|
1717
|
+
}
|
|
1718
|
+
keys() {
|
|
1719
|
+
return this.#queues.keys();
|
|
1720
|
+
}
|
|
1721
|
+
values() {
|
|
1722
|
+
return this.#queues.values();
|
|
1723
|
+
}
|
|
1724
|
+
entries() {
|
|
1725
|
+
return this.#queues.entries();
|
|
1726
|
+
}
|
|
1727
|
+
async create(options) {
|
|
1728
|
+
if (!isRecord(options)) throw new Error("Queue create options must be an object");
|
|
1729
|
+
if (this.#queues.has(options.guildId)) return this.#queues.get(options.guildId);
|
|
1730
|
+
if (!this.#player.voices.has(options.guildId)) {
|
|
1731
|
+
await this.#player.voices.connect(options.guildId, options.voiceId, options);
|
|
1732
|
+
return this.#queues.get(options.guildId);
|
|
1733
|
+
}
|
|
1734
|
+
const queue = new Queue(this.#player, options.guildId, options.context);
|
|
1735
|
+
this.#queues.set(options.guildId, queue);
|
|
1736
|
+
this.#player.emit("queueCreate", queue);
|
|
1737
|
+
return queue;
|
|
1738
|
+
}
|
|
1739
|
+
async destroy(guildId, reason = "destroyed") {
|
|
1740
|
+
if (this.#destroys.has(guildId)) return this.#destroys.get(guildId);
|
|
1741
|
+
const queue = this.#queues.get(guildId);
|
|
1742
|
+
if (!queue) return;
|
|
1743
|
+
const resolver = Promise.withResolvers();
|
|
1744
|
+
this.#destroys.set(guildId, resolver.promise);
|
|
1745
|
+
if (queue.voice.valid) await queue.rest.destroyPlayer(guildId).catch(noop);
|
|
1746
|
+
this.#cache.delete(guildId);
|
|
1747
|
+
this.#queues.delete(guildId);
|
|
1748
|
+
this.#player.emit("queueDestroy", queue, reason);
|
|
1749
|
+
await this.#player.voices.destroy(guildId, reason);
|
|
1750
|
+
this.#destroys.delete(guildId);
|
|
1751
|
+
resolver.resolve();
|
|
1752
|
+
}
|
|
1753
|
+
async relocate(node) {
|
|
1754
|
+
if (this.#relocations.has(node)) return this.#relocations.get(node);
|
|
1755
|
+
const queues = this.#queues.values().filter((q) => q.node.name === node && !q.voice.changingNode).toArray().sort((a, b) => {
|
|
1756
|
+
if (a.isPlaying && !b.isPlaying) return -1;
|
|
1757
|
+
if (b.isPlaying && !a.isPlaying) return 1;
|
|
1758
|
+
return 0;
|
|
1759
|
+
});
|
|
1760
|
+
if (queues.length === 0) return;
|
|
1761
|
+
const nodes = this.#player.nodes.relevant({ memory: 0.6, workload: 0.4 }).reduce((t, n) => {
|
|
1762
|
+
if (n.name !== node) t.push(n.name);
|
|
1763
|
+
return t;
|
|
1764
|
+
}, []);
|
|
1765
|
+
const resolver = Promise.withResolvers();
|
|
1766
|
+
this.#relocations.set(node, resolver.promise);
|
|
1767
|
+
if (nodes.length === 0) {
|
|
1768
|
+
for (const queue of queues) if (!queue.voice.valid) await queue.destroy("Session invalid, nowhere to relocate");
|
|
1769
|
+
this.#relocations.delete(node);
|
|
1770
|
+
const err = new Error("No other nodes available");
|
|
1771
|
+
resolver.reject(err);
|
|
1772
|
+
throw err;
|
|
1773
|
+
}
|
|
1774
|
+
const chunkSize = Math.floor(queues.length / nodes.length);
|
|
1775
|
+
const remaining = queues.length % nodes.length;
|
|
1776
|
+
const map = /* @__PURE__ */ new Map();
|
|
1777
|
+
if (remaining !== 0) map.set(nodes.shift(), queues.splice(0, chunkSize + remaining));
|
|
1778
|
+
while (nodes.length !== 0) map.set(nodes.shift(), queues.splice(0, chunkSize));
|
|
1779
|
+
for (const [name, queues2] of map) {
|
|
1780
|
+
for (const queue of queues2) {
|
|
1781
|
+
if (name === queue.node.name) continue;
|
|
1782
|
+
try {
|
|
1783
|
+
await queue.voice.changeNode(name);
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
await this.destroy(queue.guildId, `${err.message ?? err}`);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
this.#relocations.delete(node);
|
|
1790
|
+
resolver.resolve();
|
|
1791
|
+
}
|
|
1792
|
+
onStateUpdate(payload) {
|
|
1793
|
+
const state = payload.state;
|
|
1794
|
+
const cache = this.#cache.get(payload.guildId);
|
|
1795
|
+
if (cache !== void 0) cache.state = state;
|
|
1796
|
+
const voice = this.#player.voices.cache.get(payload.guildId);
|
|
1797
|
+
if (voice !== void 0) {
|
|
1798
|
+
voice.connected = state.connected;
|
|
1799
|
+
voice.ping = state.ping;
|
|
1800
|
+
}
|
|
1801
|
+
const queue = this.#queues.get(payload.guildId);
|
|
1802
|
+
if (!queue) return;
|
|
1803
|
+
this.#player.voices.regions.get(queue.voice.regionId)?.onPingUpdate(queue.node.name, state.ping, state.time);
|
|
1804
|
+
this.#player.emit("queueUpdate", queue, state);
|
|
1805
|
+
}
|
|
1806
|
+
async onEventUpdate(payload) {
|
|
1807
|
+
const cache = this.#cache.get(payload.guildId);
|
|
1808
|
+
if (!cache) return;
|
|
1809
|
+
const queue = this.#queues.get(payload.guildId);
|
|
1810
|
+
if (!queue) return;
|
|
1811
|
+
switch (payload.type) {
|
|
1812
|
+
case "TrackStartEvent" /* TrackStart */: {
|
|
1813
|
+
cache.track ??= payload.track;
|
|
1814
|
+
this.#player.emit("trackStart", queue, new Track(payload.track));
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
case "TrackEndEvent" /* TrackEnd */: {
|
|
1818
|
+
cache.track = null;
|
|
1819
|
+
return this.#onTrackEnd(payload, queue);
|
|
1820
|
+
}
|
|
1821
|
+
case "TrackExceptionEvent" /* TrackException */: {
|
|
1822
|
+
cache.track = null;
|
|
1823
|
+
this.#player.emit("trackError", queue, new Track(payload.track), payload.exception);
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
case "TrackStuckEvent" /* TrackStuck */: {
|
|
1827
|
+
cache.track ??= payload.track;
|
|
1828
|
+
this.#player.emit("trackStuck", queue, new Track(payload.track), payload.thresholdMs);
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
case "WebSocketClosedEvent" /* WebSocketClosed */:
|
|
1832
|
+
return this.#onVoiceClosed(payload, queue.voice);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
async #onTrackEnd(payload, queue) {
|
|
1836
|
+
const track = new Track(payload.track);
|
|
1837
|
+
switch (payload.reason) {
|
|
1838
|
+
case "cleanup" /* Cleanup */:
|
|
1839
|
+
if (payload.track.info.identifier !== queue.track?.id) break;
|
|
1840
|
+
queue.previousTracks.push(queue.tracks.shift());
|
|
1841
|
+
break;
|
|
1842
|
+
case "finished" /* Finished */:
|
|
1843
|
+
if (payload.track.info.identifier !== queue.track?.id) break;
|
|
1844
|
+
if (queue.repeatMode !== "track") queue.previousTracks.push(queue.tracks.shift());
|
|
1845
|
+
break;
|
|
1846
|
+
default:
|
|
1847
|
+
this.#player.emit("trackFinish", queue, track, payload.reason);
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
this.#player.emit("trackFinish", queue, track, payload.reason);
|
|
1851
|
+
try {
|
|
1852
|
+
if (queue.finished) {
|
|
1853
|
+
if (queue.hasPrevious && queue.repeatMode === "queue") queue.tracks.push(queue.previousTracks.shift());
|
|
1854
|
+
else if (queue.autoplay) await queue.addRelated(track);
|
|
1855
|
+
}
|
|
1856
|
+
if (queue.finished) this.#player.emit("queueFinish", queue);
|
|
1857
|
+
else await queue.resume();
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
return this.destroy(queue.guildId, `${err.message ?? err}`);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
async #onVoiceClosed(payload, voice) {
|
|
1863
|
+
let shouldReconnect;
|
|
1864
|
+
switch (payload.code) {
|
|
1865
|
+
case 4005 /* AlreadyAuthenticated */:
|
|
1866
|
+
case 4020 /* BadRequest */:
|
|
1867
|
+
case 4002 /* FailedToDecodePayload */:
|
|
1868
|
+
case 4003 /* NotAuthenticated */:
|
|
1869
|
+
case 4016 /* UnknownEncryptionMode */:
|
|
1870
|
+
case 4001 /* UnknownOpcode */:
|
|
1871
|
+
case 4012 /* UnknownProtocol */:
|
|
1872
|
+
shouldReconnect = false;
|
|
1873
|
+
break;
|
|
1874
|
+
case 4004 /* AuthenticationFailed */:
|
|
1875
|
+
case 4011 /* ServerNotFound */:
|
|
1876
|
+
case 4006 /* SessionNoLongerValid */:
|
|
1877
|
+
case 4009 /* SessionTimeout */:
|
|
1878
|
+
case 4015 /* VoiceServerCrashed */:
|
|
1879
|
+
shouldReconnect = true;
|
|
1880
|
+
break;
|
|
1881
|
+
case 4014 /* Disconnected */:
|
|
1882
|
+
case 4022 /* DisconnectedCallTerminated */:
|
|
1883
|
+
case 4021 /* DisconnectedRateLimited */:
|
|
1884
|
+
default:
|
|
1885
|
+
this.#player.emit("voiceClose", voice, payload.code, payload.reason, payload.byRemote);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
voice.reconnecting = shouldReconnect;
|
|
1889
|
+
this.#player.emit("voiceClose", voice, payload.code, payload.reason, payload.byRemote);
|
|
1890
|
+
try {
|
|
1891
|
+
if (shouldReconnect) {
|
|
1892
|
+
await voice.reconnect();
|
|
1893
|
+
if (voice.connected) return;
|
|
1894
|
+
}
|
|
1895
|
+
throw new Error(payload.reason);
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
return this.destroy(voice.guildId, err.message);
|
|
1898
|
+
} finally {
|
|
1899
|
+
voice.reconnecting = false;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
[Symbol.iterator]() {
|
|
1903
|
+
return this.#queues[Symbol.iterator]();
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
var LavaLyrics = class {
|
|
1907
|
+
name = "lavalyrics";
|
|
1908
|
+
#player;
|
|
1909
|
+
eventMap;
|
|
1910
|
+
init(player) {
|
|
1911
|
+
this.#player = player;
|
|
1912
|
+
player.on("nodeDispatch", this.#onLavaLyrics);
|
|
1913
|
+
}
|
|
1914
|
+
async fetch(track, node, skipTrackSource = false) {
|
|
1915
|
+
if (!isString(track, "non-empty")) throw new Error("Encoded track must be a non-empty string");
|
|
1916
|
+
const _node = this.#player.nodes.get(node);
|
|
1917
|
+
if (!_node) throw new Error(`Node '${node}' not found`);
|
|
1918
|
+
try {
|
|
1919
|
+
const response = await _node.rest.request("GET", "/lyrics", { params: { track, skipTrackSource } });
|
|
1920
|
+
if (response.status === HttpStatusCode.Ok) return response.data;
|
|
1921
|
+
return null;
|
|
1922
|
+
} catch {
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
async subscribe(guildId, skipTrackSource = false) {
|
|
1927
|
+
const queue = this.#player.queues.get(guildId);
|
|
1928
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
1929
|
+
if (!queue.node.ready) throw new Error(`Node '${queue.node.name}' not ready`);
|
|
1930
|
+
try {
|
|
1931
|
+
const response = await queue.rest.request(
|
|
1932
|
+
"POST",
|
|
1933
|
+
Routes.player(queue.node.sessionId, guildId) + "/lyrics/subscribe",
|
|
1934
|
+
{
|
|
1935
|
+
params: { skipTrackSource }
|
|
1936
|
+
}
|
|
1937
|
+
);
|
|
1938
|
+
return response.status === HttpStatusCode.NoContent;
|
|
1939
|
+
} catch {
|
|
1940
|
+
return false;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
async unsubscribe(guildId) {
|
|
1944
|
+
const queue = this.#player.queues.get(guildId);
|
|
1945
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
1946
|
+
if (!queue.node.ready) throw new Error(`Node '${queue.node.name}' not ready`);
|
|
1947
|
+
try {
|
|
1948
|
+
const response = await queue.rest.request(
|
|
1949
|
+
"DELETE",
|
|
1950
|
+
Routes.player(queue.node.sessionId, guildId) + "/lyrics/subscribe"
|
|
1951
|
+
);
|
|
1952
|
+
return response.status === HttpStatusCode.NoContent;
|
|
1953
|
+
} catch {
|
|
1954
|
+
return false;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
async fetchCurrent(guildId, skipTrackSource = false) {
|
|
1958
|
+
const queue = this.#player.queues.get(guildId);
|
|
1959
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
1960
|
+
if (queue.finished) return null;
|
|
1961
|
+
if (!queue.node.ready) throw new Error(`Node '${queue.node.name}' not ready`);
|
|
1962
|
+
const response = await queue.rest.request(
|
|
1963
|
+
"GET",
|
|
1964
|
+
Routes.player(queue.node.sessionId, guildId) + "/track/lyrics",
|
|
1965
|
+
{
|
|
1966
|
+
params: { skipTrackSource }
|
|
1967
|
+
}
|
|
1968
|
+
);
|
|
1969
|
+
return response.status === HttpStatusCode.Ok ? response.data : null;
|
|
1970
|
+
}
|
|
1971
|
+
#onLavaLyrics(_node, payload) {
|
|
1972
|
+
if (payload.op !== "event" /* Event */) return;
|
|
1973
|
+
const queue = this.queues.get(payload.guildId);
|
|
1974
|
+
if (!queue) return;
|
|
1975
|
+
switch (payload.type) {
|
|
1976
|
+
case "LyricsFoundEvent":
|
|
1977
|
+
this.emit("lyricsFound", queue, payload.lyrics);
|
|
1978
|
+
return;
|
|
1979
|
+
case "LyricsLineEvent":
|
|
1980
|
+
this.emit("lyricsLine", queue, payload.line, payload.lineIndex, payload.skipped);
|
|
1981
|
+
return;
|
|
1982
|
+
case "LyricsNotFoundEvent":
|
|
1983
|
+
this.emit("lyricsNotFound", queue);
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
var Player = class extends EventEmitter {
|
|
1989
|
+
#initialized = false;
|
|
1990
|
+
#clientId = null;
|
|
1991
|
+
#initPromise = null;
|
|
1992
|
+
nodes;
|
|
1993
|
+
voices;
|
|
1994
|
+
queues;
|
|
1995
|
+
options;
|
|
1996
|
+
plugins;
|
|
1997
|
+
constructor(options) {
|
|
1998
|
+
super({ captureRejections: false });
|
|
1999
|
+
const _options = { ...DefaultPlayerOptions, ...options };
|
|
2000
|
+
if (isRecord(_options, "non-empty")) this.options = _options;
|
|
2001
|
+
else throw new Error("Player options must be a non-empty object");
|
|
2002
|
+
if (_options.nodes.length === 0) throw new Error("Missing node options");
|
|
2003
|
+
this.nodes = new NodeManager(this);
|
|
2004
|
+
this.voices = new VoiceManager(this);
|
|
2005
|
+
this.queues = new QueueManager(this);
|
|
2006
|
+
this.plugins = {};
|
|
2007
|
+
if (_options.plugins !== void 0) {
|
|
2008
|
+
for (const plugin of _options.plugins) this.plugins[plugin.name] = plugin;
|
|
2009
|
+
delete _options.plugins;
|
|
2010
|
+
}
|
|
2011
|
+
const immutable = {
|
|
2012
|
+
writable: false,
|
|
2013
|
+
configurable: false
|
|
2014
|
+
};
|
|
2015
|
+
Object.defineProperties(this, {
|
|
2016
|
+
nodes: immutable,
|
|
2017
|
+
voices: immutable,
|
|
2018
|
+
queues: immutable,
|
|
2019
|
+
options: immutable,
|
|
2020
|
+
plugins: immutable
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
get clientId() {
|
|
2024
|
+
return this.#clientId;
|
|
2025
|
+
}
|
|
2026
|
+
get initialized() {
|
|
2027
|
+
return this.#initialized;
|
|
2028
|
+
}
|
|
2029
|
+
async init(clientId) {
|
|
2030
|
+
if (this.#initPromise !== null) return this.#initPromise;
|
|
2031
|
+
if (this.#initialized) return;
|
|
2032
|
+
const resolver = Promise.withResolvers();
|
|
2033
|
+
this.#initPromise = resolver.promise;
|
|
2034
|
+
this.#clientId = clientId;
|
|
2035
|
+
try {
|
|
2036
|
+
for (const node of this.options.nodes) this.nodes.create(node);
|
|
2037
|
+
for (const name in this.plugins) this.plugins[name].init(this);
|
|
2038
|
+
await this.nodes.connect();
|
|
2039
|
+
this.#initialized = true;
|
|
2040
|
+
this.emit("init");
|
|
2041
|
+
resolver.resolve();
|
|
2042
|
+
} catch (err) {
|
|
2043
|
+
resolver.reject(err);
|
|
2044
|
+
throw err;
|
|
2045
|
+
} finally {
|
|
2046
|
+
this.#initPromise = null;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
getQueue(guildId) {
|
|
2050
|
+
return this.queues.get(guildId);
|
|
2051
|
+
}
|
|
2052
|
+
async createQueue(options) {
|
|
2053
|
+
return this.queues.create(options);
|
|
2054
|
+
}
|
|
2055
|
+
async destroyQueue(guildId, reason) {
|
|
2056
|
+
return this.queues.destroy(guildId, reason);
|
|
2057
|
+
}
|
|
2058
|
+
async search(query, options) {
|
|
2059
|
+
if (!isString(query, "non-empty")) throw new Error("Query must be a non-empty string");
|
|
2060
|
+
const node = options?.node !== void 0 ? this.nodes.get(options.node) : this.nodes.relevant()[0];
|
|
2061
|
+
if (!node) {
|
|
2062
|
+
if (options?.node === void 0) throw new Error("No nodes available");
|
|
2063
|
+
throw new Error(`Node '${options.node}' not found`);
|
|
2064
|
+
}
|
|
2065
|
+
query = isString(query, "url") ? query : `${options?.prefix ?? this.options.queryPrefix}:${query}`;
|
|
2066
|
+
const result = await node.rest.loadTracks(query);
|
|
2067
|
+
switch (result.loadType) {
|
|
2068
|
+
case "empty" /* Empty */:
|
|
2069
|
+
return { type: "empty", data: [] };
|
|
2070
|
+
case "error" /* Error */:
|
|
2071
|
+
return { type: "error", data: result.data };
|
|
2072
|
+
case "playlist" /* Playlist */:
|
|
2073
|
+
return { type: "playlist", data: new Playlist(result.data) };
|
|
2074
|
+
case "search" /* Search */:
|
|
2075
|
+
return { type: "query", data: result.data.map((t) => new Track(t)) };
|
|
2076
|
+
case "track" /* Track */:
|
|
2077
|
+
return { type: "track", data: new Track(result.data) };
|
|
2078
|
+
default:
|
|
2079
|
+
throw new Error(`Unexpected load result type from node '${node.name}'`);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
async play(source, options) {
|
|
2083
|
+
let queue = this.queues.get(options.guildId);
|
|
2084
|
+
if (typeof source === "string") {
|
|
2085
|
+
let result;
|
|
2086
|
+
if (!queue) result = await this.search(source, options);
|
|
2087
|
+
else result = await queue.search(source, options.prefix);
|
|
2088
|
+
if (result.type === "empty") throw new Error(`No results found for '${source}'`);
|
|
2089
|
+
if (result.type === "error") throw new Error(result.data.message ?? result.data.cause, { cause: result.data });
|
|
2090
|
+
source = result.type === "query" ? result.data[0] : result.data;
|
|
2091
|
+
}
|
|
2092
|
+
queue ??= await this.queues.create(options);
|
|
2093
|
+
if (options.context !== void 0) Object.assign(queue.context, options.context);
|
|
2094
|
+
queue.add(source, options.userData);
|
|
2095
|
+
if (queue.stopped) await queue.resume();
|
|
2096
|
+
return queue;
|
|
2097
|
+
}
|
|
2098
|
+
async jump(guildId, index) {
|
|
2099
|
+
const queue = this.queues.get(guildId);
|
|
2100
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2101
|
+
return queue.jump(index);
|
|
2102
|
+
}
|
|
2103
|
+
async pause(guildId) {
|
|
2104
|
+
const queue = this.queues.get(guildId);
|
|
2105
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2106
|
+
return queue.pause();
|
|
2107
|
+
}
|
|
2108
|
+
async previous(guildId) {
|
|
2109
|
+
const queue = this.queues.get(guildId);
|
|
2110
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2111
|
+
return queue.previous();
|
|
2112
|
+
}
|
|
2113
|
+
async resume(guildId) {
|
|
2114
|
+
const queue = this.queues.get(guildId);
|
|
2115
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2116
|
+
return queue.resume();
|
|
2117
|
+
}
|
|
2118
|
+
async seek(guildId, ms) {
|
|
2119
|
+
const queue = this.queues.get(guildId);
|
|
2120
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2121
|
+
return queue.seek(ms);
|
|
2122
|
+
}
|
|
2123
|
+
setAutoplay(guildId, autoplay) {
|
|
2124
|
+
const queue = this.queues.get(guildId);
|
|
2125
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2126
|
+
return queue.setAutoplay(autoplay);
|
|
2127
|
+
}
|
|
2128
|
+
setRepeatMode(guildId, repeatMode) {
|
|
2129
|
+
const queue = this.queues.get(guildId);
|
|
2130
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2131
|
+
return queue.setRepeatMode(repeatMode);
|
|
2132
|
+
}
|
|
2133
|
+
async setVolume(guildId, volume) {
|
|
2134
|
+
const queue = this.queues.get(guildId);
|
|
2135
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2136
|
+
return queue.setVolume(volume);
|
|
2137
|
+
}
|
|
2138
|
+
shuffle(guildId) {
|
|
2139
|
+
const queue = this.queues.get(guildId);
|
|
2140
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2141
|
+
return queue.shuffle();
|
|
2142
|
+
}
|
|
2143
|
+
async next(guildId) {
|
|
2144
|
+
const queue = this.queues.get(guildId);
|
|
2145
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2146
|
+
return queue.next();
|
|
2147
|
+
}
|
|
2148
|
+
async stop(guildId) {
|
|
2149
|
+
const queue = this.queues.get(guildId);
|
|
2150
|
+
if (!queue) throw new Error(`No queue found for guild '${guildId}'`);
|
|
2151
|
+
return queue.stop();
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
|
|
2155
|
+
// src/index.ts
|
|
2156
|
+
var version = "1.0.0";
|
|
2157
|
+
|
|
2158
|
+
export { CloseCodes, DefaultNodeOptions, DefaultPlayerOptions, DefaultRestOptions, EventType, FilterManager, IPBlockType, LavaLyrics, LoadType, Node, NodeManager, OPType, Player, Playlist, Queue, QueueManager, REST, RoutePlannerType, Routes, Severity, SnowflakeRegex, Track, TrackEndReason, VoiceCloseCodes, VoiceManager, VoiceRegion, VoiceRegionIdRegex, VoiceState, formatDuration, isArray, isNumber, isRecord, isString, noop, version };
|