lavalink-client 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -152
- package/dist/cjs/index.d.ts +9 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/structures/Filters.d.ts +231 -0
- package/dist/cjs/structures/Filters.js +481 -0
- package/dist/cjs/structures/LavalinkManager.d.ts +124 -0
- package/dist/cjs/structures/LavalinkManager.js +168 -0
- package/dist/cjs/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/cjs/structures/LavalinkManagerStatics.js +84 -0
- package/dist/cjs/structures/Node.d.ts +245 -0
- package/dist/cjs/structures/Node.js +602 -0
- package/dist/cjs/structures/NodeManager.d.ts +61 -0
- package/dist/cjs/structures/NodeManager.js +35 -0
- package/dist/cjs/structures/Player.d.ts +191 -0
- package/dist/cjs/structures/Player.js +395 -0
- package/dist/cjs/structures/Queue.d.ts +107 -0
- package/dist/cjs/structures/Queue.js +215 -0
- package/dist/cjs/structures/Track.d.ts +47 -0
- package/dist/cjs/structures/Track.js +2 -0
- package/dist/cjs/structures/Utils.d.ts +258 -0
- package/dist/cjs/structures/Utils.js +179 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/structures/Filters.d.ts +231 -0
- package/dist/esm/structures/Filters.js +477 -0
- package/dist/esm/structures/LavalinkManager.d.ts +124 -0
- package/dist/esm/structures/LavalinkManager.js +164 -0
- package/dist/esm/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/esm/structures/LavalinkManagerStatics.js +81 -0
- package/dist/esm/structures/Node.d.ts +245 -0
- package/dist/esm/structures/Node.js +597 -0
- package/dist/esm/structures/NodeManager.d.ts +61 -0
- package/dist/esm/structures/NodeManager.js +31 -0
- package/dist/esm/structures/Player.d.ts +191 -0
- package/dist/esm/structures/Player.js +391 -0
- package/dist/esm/structures/Queue.d.ts +107 -0
- package/dist/esm/structures/Queue.js +208 -0
- package/dist/esm/structures/Track.d.ts +47 -0
- package/dist/esm/structures/Track.js +1 -0
- package/dist/esm/structures/Utils.d.ts +258 -0
- package/dist/esm/structures/Utils.js +173 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +13 -0
- package/dist/structures/Filters.d.ts +230 -0
- package/dist/structures/Filters.js +472 -0
- package/dist/structures/LavalinkManager.d.ts +47 -0
- package/dist/structures/LavalinkManager.js +36 -0
- package/dist/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/structures/LavalinkManagerStatics.js +76 -0
- package/dist/structures/Node.d.ts +171 -0
- package/dist/structures/Node.js +462 -0
- package/dist/structures/NodeManager.d.ts +58 -0
- package/dist/structures/NodeManager.js +25 -0
- package/dist/structures/Player.d.ts +101 -0
- package/dist/structures/Player.js +232 -0
- package/dist/structures/PlayerManager.d.ts +62 -0
- package/dist/structures/PlayerManager.js +26 -0
- package/dist/structures/Queue.d.ts +93 -0
- package/dist/structures/Queue.js +160 -0
- package/dist/structures/QueueManager.d.ts +77 -0
- package/dist/structures/QueueManager.js +74 -0
- package/dist/structures/Track.d.ts +27 -0
- package/dist/structures/Track.js +2 -0
- package/dist/structures/Utils.d.ts +183 -0
- package/dist/structures/Utils.js +43 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/structures/Filters.d.ts +231 -0
- package/dist/types/structures/LavalinkManager.d.ts +124 -0
- package/dist/types/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/types/structures/Node.d.ts +245 -0
- package/dist/types/structures/NodeManager.d.ts +61 -0
- package/dist/types/structures/Player.d.ts +191 -0
- package/dist/types/structures/Queue.d.ts +107 -0
- package/dist/types/structures/Track.d.ts +47 -0
- package/dist/types/structures/Utils.d.ts +258 -0
- package/package.json +63 -26
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LavalinkNode = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const ws_1 = tslib_1.__importDefault(require("ws"));
|
|
6
|
+
const undici_1 = require("undici");
|
|
7
|
+
const Utils_1 = require("./Utils");
|
|
8
|
+
const Player_1 = require("./Player");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
class LavalinkNode {
|
|
11
|
+
/** The provided Options of the Node */
|
|
12
|
+
options;
|
|
13
|
+
/** The amount of rest calls the node has made. */
|
|
14
|
+
calls = 0;
|
|
15
|
+
stats = {
|
|
16
|
+
players: 0,
|
|
17
|
+
playingPlayers: 0,
|
|
18
|
+
cpu: {
|
|
19
|
+
cores: 0,
|
|
20
|
+
lavalinkLoad: 0,
|
|
21
|
+
systemLoad: 0
|
|
22
|
+
},
|
|
23
|
+
memory: {
|
|
24
|
+
allocated: 0,
|
|
25
|
+
free: 0,
|
|
26
|
+
reservable: 0,
|
|
27
|
+
used: 0,
|
|
28
|
+
},
|
|
29
|
+
uptime: 0,
|
|
30
|
+
frameStats: {
|
|
31
|
+
deficit: 0,
|
|
32
|
+
nulled: 0,
|
|
33
|
+
sent: 0,
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
sessionId = null;
|
|
37
|
+
/** Actual Lavalink Information of the Node */
|
|
38
|
+
info = null;
|
|
39
|
+
/** The Node Manager of this Node */
|
|
40
|
+
NodeManager = null;
|
|
41
|
+
/** The Reconnection Timeout */
|
|
42
|
+
reconnectTimeout = undefined;
|
|
43
|
+
/** The Reconnection Attempt counter */
|
|
44
|
+
reconnectAttempts = 1;
|
|
45
|
+
/** The Socket of the Lavalink */
|
|
46
|
+
socket = null;
|
|
47
|
+
/** The Rest Server for this Lavalink */
|
|
48
|
+
rest;
|
|
49
|
+
/** Version of what the Lavalink Server should be */
|
|
50
|
+
version = "v4";
|
|
51
|
+
/**
|
|
52
|
+
* Create a new Node
|
|
53
|
+
* @param options
|
|
54
|
+
* @param manager
|
|
55
|
+
*/
|
|
56
|
+
constructor(options, manager) {
|
|
57
|
+
this.options = {
|
|
58
|
+
secure: false,
|
|
59
|
+
retryAmount: 5,
|
|
60
|
+
retryDelay: 30e3,
|
|
61
|
+
requestTimeout: 10e3,
|
|
62
|
+
...options
|
|
63
|
+
};
|
|
64
|
+
this.NodeManager = manager;
|
|
65
|
+
this.validate();
|
|
66
|
+
if (this.options.secure && this.options.port !== 443)
|
|
67
|
+
throw new SyntaxError("If secure is true, then the port must be 443");
|
|
68
|
+
this.rest = new undici_1.Pool(this.poolAddress, this.options.poolOptions);
|
|
69
|
+
this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Makes an API call to the Node
|
|
73
|
+
* @param endpoint The endpoint that we will make the call to
|
|
74
|
+
* @param modify Used to modify the request before being sent
|
|
75
|
+
* @returns The returned data
|
|
76
|
+
*/
|
|
77
|
+
async request(endpoint, modify, parseAsText = false) {
|
|
78
|
+
const options = {
|
|
79
|
+
path: `/${this.version}/${endpoint.replace(/^\//gm, "")}`,
|
|
80
|
+
method: "GET",
|
|
81
|
+
headers: {
|
|
82
|
+
Authorization: this.options.authorization
|
|
83
|
+
},
|
|
84
|
+
headersTimeout: this.options.requestTimeout,
|
|
85
|
+
};
|
|
86
|
+
modify?.(options);
|
|
87
|
+
const url = new URL(`${this.poolAddress}${options.path}`);
|
|
88
|
+
url.searchParams.append("trace", "true");
|
|
89
|
+
options.path = url.toString().replace(this.poolAddress, "");
|
|
90
|
+
const request = await this.rest.request(options);
|
|
91
|
+
this.calls++;
|
|
92
|
+
if (options.method === "DELETE")
|
|
93
|
+
return;
|
|
94
|
+
return parseAsText ? await request.body.text() : await request.body.json();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Update the Player State on the Lavalink Server
|
|
98
|
+
* @param data
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
101
|
+
async updatePlayer(data) {
|
|
102
|
+
if (!this.sessionId)
|
|
103
|
+
throw new Error("The Lavalink Node is either not ready, or not up to date!");
|
|
104
|
+
this.syncPlayerData(data);
|
|
105
|
+
const res = await this.request(`/sessions/${this.sessionId}/players/${data.guildId}`, r => {
|
|
106
|
+
r.method = "PATCH";
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
108
|
+
r.headers["Content-Type"] = "application/json";
|
|
109
|
+
if (data.playerOptions.track)
|
|
110
|
+
delete data.playerOptions.track;
|
|
111
|
+
r.body = JSON.stringify(data.playerOptions);
|
|
112
|
+
if (data.noReplace) {
|
|
113
|
+
const url = new URL(`${this.poolAddress}${r.path}`);
|
|
114
|
+
url.searchParams.append("noReplace", data.noReplace?.toString() || "false");
|
|
115
|
+
r.path = url.toString().replace(this.poolAddress, "");
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return this.syncPlayerData({}, res), res;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Destroys the Player on the Lavalink Server
|
|
122
|
+
* @param guildId
|
|
123
|
+
* @returns
|
|
124
|
+
*/
|
|
125
|
+
async destroyPlayer(guildId) {
|
|
126
|
+
if (!this.sessionId)
|
|
127
|
+
throw new Error("The Lavalink-Node is either not ready, or not up to date!");
|
|
128
|
+
return await this.request(`/sessions/${this.sessionId}/players/${guildId}`, r => { r.method = "DELETE"; });
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Connect to the Lavalink Node
|
|
132
|
+
* @param sessionId Provide the Session Id of the previous connection, to resume the node and it's player(s)
|
|
133
|
+
* @returns
|
|
134
|
+
*/
|
|
135
|
+
connect(sessionId) {
|
|
136
|
+
if (this.connected)
|
|
137
|
+
return;
|
|
138
|
+
const headers = {
|
|
139
|
+
Authorization: this.options.authorization,
|
|
140
|
+
"Num-Shards": String(this.NodeManager.LavalinkManager.options.client.shards || "auto"),
|
|
141
|
+
"User-Id": this.NodeManager.LavalinkManager.options.client.id,
|
|
142
|
+
"User-Name": this.NodeManager.LavalinkManager.options.client.username || "Lavalink-Client",
|
|
143
|
+
};
|
|
144
|
+
if (typeof this.options.sessionId === "string" || typeof sessionId === "string") {
|
|
145
|
+
headers["Session-Id"] = this.options.sessionId || sessionId;
|
|
146
|
+
this.sessionId = this.options.sessionId || sessionId;
|
|
147
|
+
}
|
|
148
|
+
this.socket = new ws_1.default(`ws${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}/v4/websocket`, { headers });
|
|
149
|
+
this.socket.on("open", this.open.bind(this));
|
|
150
|
+
this.socket.on("close", this.close.bind(this));
|
|
151
|
+
this.socket.on("message", this.message.bind(this));
|
|
152
|
+
this.socket.on("error", this.error.bind(this));
|
|
153
|
+
}
|
|
154
|
+
/** Get the id of the node */
|
|
155
|
+
get id() {
|
|
156
|
+
return this.options.id || this.options.host;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Destroys the Node-Connection (Websocket) and all player's of the node
|
|
160
|
+
* @returns
|
|
161
|
+
*/
|
|
162
|
+
destroy(destroyReason) {
|
|
163
|
+
if (!this.connected)
|
|
164
|
+
return;
|
|
165
|
+
const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id == this.id);
|
|
166
|
+
if (players)
|
|
167
|
+
players.forEach(p => p.destroy(destroyReason || Player_1.DestroyReasons.NodeDestroy));
|
|
168
|
+
this.socket.close(1000, "destroy");
|
|
169
|
+
this.socket.removeAllListeners();
|
|
170
|
+
this.socket = null;
|
|
171
|
+
this.reconnectAttempts = 1;
|
|
172
|
+
clearTimeout(this.reconnectTimeout);
|
|
173
|
+
this.NodeManager.emit("destroy", this, destroyReason);
|
|
174
|
+
this.NodeManager.nodes.delete(this.id);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
/** Returns if connected to the Node. */
|
|
178
|
+
get connected() {
|
|
179
|
+
if (!this.socket)
|
|
180
|
+
return false;
|
|
181
|
+
return this.socket.readyState === ws_1.default.OPEN;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Gets all Players of a Node
|
|
185
|
+
*/
|
|
186
|
+
async fetchAllPlayers() {
|
|
187
|
+
if (!this.sessionId)
|
|
188
|
+
throw new Error("The Lavalink-Node is either not ready, or not up to date!");
|
|
189
|
+
const players = await this.request(`/sessions/${this.sessionId}/players`);
|
|
190
|
+
if (!Array.isArray(players))
|
|
191
|
+
return [];
|
|
192
|
+
else
|
|
193
|
+
return players;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Gets specific Player Information
|
|
197
|
+
*/
|
|
198
|
+
async fetchPlayer(guildId) {
|
|
199
|
+
if (!this.sessionId)
|
|
200
|
+
throw new Error("The Lavalink-Node is either not ready, or not up to date!");
|
|
201
|
+
return await this.request(`/sessions/${this.sessionId}/players/${guildId}`);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Updates the session with and enables/disables resuming and timeout
|
|
205
|
+
* @param resuming Whether resuming is enabled for this session or not
|
|
206
|
+
* @param timeout The timeout in seconds (default is 60s)
|
|
207
|
+
*/
|
|
208
|
+
async updateSession(resuming, timeout) {
|
|
209
|
+
if (!this.sessionId)
|
|
210
|
+
throw new Error("the Lavalink-Node is either not ready, or not up to date!");
|
|
211
|
+
const data = {};
|
|
212
|
+
if (typeof resuming === "boolean")
|
|
213
|
+
data.resuming = resuming;
|
|
214
|
+
if (typeof timeout === "number" && timeout > 0)
|
|
215
|
+
data.timeout = timeout;
|
|
216
|
+
return await this.request(`/sessions/${this.sessionId}`, r => {
|
|
217
|
+
r.method = "PATCH";
|
|
218
|
+
r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
|
|
219
|
+
r.body = JSON.stringify(data);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Decode Track or Tracks
|
|
224
|
+
*/
|
|
225
|
+
decode = {
|
|
226
|
+
/**
|
|
227
|
+
* Decode a single track into its info, where BASE64 is the encoded base64 data.
|
|
228
|
+
* @param encoded
|
|
229
|
+
* @returns
|
|
230
|
+
*/
|
|
231
|
+
singleTrack: async (encoded) => {
|
|
232
|
+
if (!encoded)
|
|
233
|
+
throw new SyntaxError("No encoded (Base64 string) was provided");
|
|
234
|
+
return await this.request(`/decodetrack?encodedTrack=${encoded}`);
|
|
235
|
+
},
|
|
236
|
+
/**
|
|
237
|
+
*
|
|
238
|
+
* @param encodeds Decodes multiple tracks into their info
|
|
239
|
+
* @returns
|
|
240
|
+
*/
|
|
241
|
+
multipleTracks: async (encodeds) => {
|
|
242
|
+
if (!Array.isArray(encodeds) || !encodeds.every(v => typeof v === "string" && v.length > 1))
|
|
243
|
+
throw new SyntaxError("You need to provide encodeds, which is an array of base64 strings");
|
|
244
|
+
return await this.request(`/decodetracks`, r => {
|
|
245
|
+
r.method = "POST";
|
|
246
|
+
r.body = JSON.stringify(encodeds);
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
248
|
+
r.headers["Content-Type"] = "application/json";
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Request Lavalink statistics.
|
|
254
|
+
* @returns
|
|
255
|
+
*/
|
|
256
|
+
async fetchStats() {
|
|
257
|
+
return await this.request(`/stats`);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Request Lavalink version.
|
|
261
|
+
* @returns
|
|
262
|
+
*/
|
|
263
|
+
async fetchVersion() {
|
|
264
|
+
return await this.request(`/version`, r => { r.path = "/version"; }, true);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Request Lavalink information.
|
|
268
|
+
* @returns
|
|
269
|
+
*/
|
|
270
|
+
async fetchInfo() {
|
|
271
|
+
return await this.request(`/info`);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Lavalink's Route Planner Api
|
|
275
|
+
*/
|
|
276
|
+
routePlannerApi = {
|
|
277
|
+
/**
|
|
278
|
+
* Get routplanner Info from Lavalink
|
|
279
|
+
*/
|
|
280
|
+
getStatus: async () => {
|
|
281
|
+
if (!this.sessionId)
|
|
282
|
+
throw new Error("the Lavalink-Node is either not ready, or not up to date!");
|
|
283
|
+
return await this.request(`/routeplanner/status`);
|
|
284
|
+
},
|
|
285
|
+
/**
|
|
286
|
+
* Release blacklisted IP address into pool of IPs
|
|
287
|
+
* @param address IP address
|
|
288
|
+
*/
|
|
289
|
+
unmarkFailedAddress: async (address) => {
|
|
290
|
+
if (!this.sessionId)
|
|
291
|
+
throw new Error("the Lavalink-Node is either not ready, or not up to date!");
|
|
292
|
+
await this.request(`/routeplanner/free/address`, r => {
|
|
293
|
+
r.method = "POST";
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
295
|
+
r.headers["Content-Type"] = "application/json";
|
|
296
|
+
r.body = JSON.stringify({ address });
|
|
297
|
+
});
|
|
298
|
+
},
|
|
299
|
+
/**
|
|
300
|
+
* Release all blacklisted IP addresses into pool of IPs
|
|
301
|
+
*/
|
|
302
|
+
unmarkAllFailedAddresses: async () => {
|
|
303
|
+
if (!this.sessionId)
|
|
304
|
+
throw new Error("the Lavalink-Node is either not ready, or not up to date!");
|
|
305
|
+
return await this.request(`/routeplanner/free/all`, r => {
|
|
306
|
+
r.method = "POST";
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
308
|
+
r.headers["Content-Type"] = "application/json";
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
/** Private Utils */
|
|
313
|
+
validate() {
|
|
314
|
+
if (!this.options.authorization)
|
|
315
|
+
throw new SyntaxError("LavalinkNode requires 'authorization'");
|
|
316
|
+
if (!this.options.host)
|
|
317
|
+
throw new SyntaxError("LavalinkNode requires 'host'");
|
|
318
|
+
if (!this.options.port)
|
|
319
|
+
throw new SyntaxError("LavalinkNode requires 'port'");
|
|
320
|
+
}
|
|
321
|
+
syncPlayerData(data, res) {
|
|
322
|
+
if (typeof data === "object" && typeof data?.guildId === "string" && typeof data.playerOptions === "object" && Object.keys(data.playerOptions).length > 1) {
|
|
323
|
+
const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
|
|
324
|
+
if (!player)
|
|
325
|
+
return;
|
|
326
|
+
if (typeof data.playerOptions.paused !== "undefined") {
|
|
327
|
+
player.paused = data.playerOptions.paused;
|
|
328
|
+
player.playing = !data.playerOptions.paused;
|
|
329
|
+
}
|
|
330
|
+
if (typeof data.playerOptions.position === "number") {
|
|
331
|
+
player.position = data.playerOptions.position;
|
|
332
|
+
player.lastPosition = data.playerOptions.position;
|
|
333
|
+
}
|
|
334
|
+
if (typeof data.playerOptions.voice !== "undefined")
|
|
335
|
+
player.voice = data.playerOptions.voice;
|
|
336
|
+
if (typeof data.playerOptions.volume !== "undefined") {
|
|
337
|
+
if (this.NodeManager.LavalinkManager.options.playerOptions.volumeDecrementer) {
|
|
338
|
+
player.volume = data.playerOptions.volume / this.NodeManager.LavalinkManager.options.playerOptions.volumeDecrementer;
|
|
339
|
+
player.lavalinkVolume = data.playerOptions.volume;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
player.volume = data.playerOptions.volume;
|
|
343
|
+
player.lavalinkVolume = data.playerOptions.volume;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (typeof data.playerOptions.filters !== "undefined") {
|
|
347
|
+
const oldFilterTimescale = { ...(player.filterManager.data.timescale || {}) };
|
|
348
|
+
Object.freeze(oldFilterTimescale);
|
|
349
|
+
if (data.playerOptions.filters.timescale)
|
|
350
|
+
player.filterManager.data.timescale = data.playerOptions.filters.timescale;
|
|
351
|
+
if (data.playerOptions.filters.distortion)
|
|
352
|
+
player.filterManager.data.distortion = data.playerOptions.filters.distortion;
|
|
353
|
+
if (data.playerOptions.filters.echo)
|
|
354
|
+
player.filterManager.data.echo = data.playerOptions.filters.echo;
|
|
355
|
+
if (data.playerOptions.filters.vibrato)
|
|
356
|
+
player.filterManager.data.vibrato = data.playerOptions.filters.vibrato;
|
|
357
|
+
if (data.playerOptions.filters.volume)
|
|
358
|
+
player.filterManager.data.volume = data.playerOptions.filters.volume;
|
|
359
|
+
if (data.playerOptions.filters.equalizer)
|
|
360
|
+
player.filterManager.data.equalizer = data.playerOptions.filters.equalizer;
|
|
361
|
+
if (data.playerOptions.filters.karaoke)
|
|
362
|
+
player.filterManager.data.karaoke = data.playerOptions.filters.karaoke;
|
|
363
|
+
if (data.playerOptions.filters.lowPass)
|
|
364
|
+
player.filterManager.data.lowPass = data.playerOptions.filters.lowPass;
|
|
365
|
+
if (data.playerOptions.filters.rotation)
|
|
366
|
+
player.filterManager.data.rotation = data.playerOptions.filters.rotation;
|
|
367
|
+
if (data.playerOptions.filters.tremolo)
|
|
368
|
+
player.filterManager.data.tremolo = data.playerOptions.filters.tremolo;
|
|
369
|
+
player.filterManager.checkFiltersState(oldFilterTimescale);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// just for res
|
|
373
|
+
if (res?.guildId === "string" && typeof res?.voice !== "undefined") {
|
|
374
|
+
const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
|
|
375
|
+
if (!player)
|
|
376
|
+
return;
|
|
377
|
+
if (typeof res?.voice?.connected === "boolean" && res.voice.connected === false)
|
|
378
|
+
return player.destroy(Player_1.DestroyReasons.LavalinkNoVoice);
|
|
379
|
+
player.ping.ws = res?.voice?.ping || player?.ping.ws;
|
|
380
|
+
}
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
get poolAddress() {
|
|
384
|
+
return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
|
|
385
|
+
}
|
|
386
|
+
reconnect() {
|
|
387
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
388
|
+
if (this.reconnectAttempts >= this.options.retryAmount) {
|
|
389
|
+
const error = new Error(`Unable to connect after ${this.options.retryAmount} attempts.`);
|
|
390
|
+
this.NodeManager.emit("error", this, error);
|
|
391
|
+
return this.destroy(Player_1.DestroyReasons.NodeReconnectFail);
|
|
392
|
+
}
|
|
393
|
+
this.socket.removeAllListeners();
|
|
394
|
+
this.socket = null;
|
|
395
|
+
this.NodeManager.emit("reconnecting", this);
|
|
396
|
+
this.connect();
|
|
397
|
+
this.reconnectAttempts++;
|
|
398
|
+
}, this.options.retryDelay || 1000);
|
|
399
|
+
}
|
|
400
|
+
open() {
|
|
401
|
+
if (this.reconnectTimeout)
|
|
402
|
+
clearTimeout(this.reconnectTimeout);
|
|
403
|
+
// reset the reconnect attempts amount
|
|
404
|
+
this.reconnectAttempts = 1;
|
|
405
|
+
this.NodeManager.emit("connect", this);
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
this.fetchInfo().then(x => this.info = x).catch(() => null).then(() => {
|
|
408
|
+
if (!this.info && ["v3", "v4"].includes(this.version)) {
|
|
409
|
+
const errorString = `Lavalink Node (${this.poolAddress}) does not provide any /${this.version}/info`;
|
|
410
|
+
throw new Error(errorString);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}, 1500);
|
|
414
|
+
}
|
|
415
|
+
close(code, reason) {
|
|
416
|
+
this.NodeManager.emit("disconnect", this, { code, reason });
|
|
417
|
+
if (code !== 1000 || reason !== "destroy")
|
|
418
|
+
this.reconnect();
|
|
419
|
+
}
|
|
420
|
+
error(error) {
|
|
421
|
+
if (!error)
|
|
422
|
+
return;
|
|
423
|
+
this.NodeManager.emit("error", this, error);
|
|
424
|
+
}
|
|
425
|
+
async message(d) {
|
|
426
|
+
if (Array.isArray(d))
|
|
427
|
+
d = Buffer.concat(d);
|
|
428
|
+
else if (d instanceof ArrayBuffer)
|
|
429
|
+
d = Buffer.from(d);
|
|
430
|
+
const payload = JSON.parse(d.toString());
|
|
431
|
+
if (!payload.op)
|
|
432
|
+
return;
|
|
433
|
+
this.NodeManager.emit("raw", this, payload);
|
|
434
|
+
switch (payload.op) {
|
|
435
|
+
case "stats":
|
|
436
|
+
delete payload.op;
|
|
437
|
+
this.stats = { ...payload };
|
|
438
|
+
break;
|
|
439
|
+
case "playerUpdate":
|
|
440
|
+
const player = this.NodeManager.LavalinkManager.getPlayer(payload.guildId);
|
|
441
|
+
if (!player)
|
|
442
|
+
return;
|
|
443
|
+
if (player.get("internal_updateInterval"))
|
|
444
|
+
clearInterval(player.get("internal_updateInterval"));
|
|
445
|
+
// override the position
|
|
446
|
+
player.position = payload.state.position || 0;
|
|
447
|
+
player.lastPosition = payload.state.position || 0;
|
|
448
|
+
player.connected = payload.state.connected;
|
|
449
|
+
player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
|
|
450
|
+
if (!player.createdTimeStamp && payload.state.time)
|
|
451
|
+
player.createdTimeStamp = payload.state.time;
|
|
452
|
+
if (typeof this.NodeManager.LavalinkManager.options.playerOptions.clientBasedUpdateInterval === "number" && this.NodeManager.LavalinkManager.options.playerOptions.clientBasedUpdateInterval >= 10) {
|
|
453
|
+
player.set("internal_updateInterval", setInterval(() => {
|
|
454
|
+
player.position += this.NodeManager.LavalinkManager.options.playerOptions.clientBasedUpdateInterval || 250;
|
|
455
|
+
if (player.filterManager.filterUpdatedState >= 1) {
|
|
456
|
+
player.filterManager.filterUpdatedState++;
|
|
457
|
+
const maxMins = 8;
|
|
458
|
+
const currentDuration = player.queue.current?.info?.duration || 0;
|
|
459
|
+
if (currentDuration <= maxMins * 6e4 || (0, path_1.isAbsolute)(player.queue.current?.info?.uri)) {
|
|
460
|
+
if (player.filterManager.filterUpdatedState >= ((this.NodeManager.LavalinkManager.options.playerOptions.clientBasedUpdateInterval || 250) > 400 ? 2 : 3)) {
|
|
461
|
+
player.filterManager.filterUpdatedState = 0;
|
|
462
|
+
player.seek(player.position);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
player.filterManager.filterUpdatedState = 0;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}, this.NodeManager.LavalinkManager.options.playerOptions.clientBasedUpdateInterval || 250));
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
if (player.filterManager.filterUpdatedState >= 1) { // if no interval but instafix available, findable via the "filterUpdatedState" property
|
|
473
|
+
const maxMins = 8;
|
|
474
|
+
const currentDuration = player.queue.current?.info?.duration || 0;
|
|
475
|
+
if (currentDuration <= maxMins * 6e4 || (0, path_1.isAbsolute)(player.queue.current?.info?.uri))
|
|
476
|
+
player.seek(player.position);
|
|
477
|
+
player.filterManager.filterUpdatedState = 0;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
case "event":
|
|
482
|
+
this.handleEvent(payload);
|
|
483
|
+
break;
|
|
484
|
+
case "ready": // payload: { resumed: false, sessionId: 'ytva350aevn6n9n8', op: 'ready' }
|
|
485
|
+
this.sessionId = payload.sessionId;
|
|
486
|
+
break;
|
|
487
|
+
default:
|
|
488
|
+
this.NodeManager.emit("error", this, new Error(`Unexpected op "${payload.op}" with data`), payload);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
async handleEvent(payload) {
|
|
493
|
+
if (!payload.guildId)
|
|
494
|
+
return;
|
|
495
|
+
const player = this.NodeManager.LavalinkManager.getPlayer(payload.guildId);
|
|
496
|
+
if (!player)
|
|
497
|
+
return;
|
|
498
|
+
switch (payload.type) {
|
|
499
|
+
case "TrackStartEvent":
|
|
500
|
+
this.trackStart(player, player.queue.current, payload);
|
|
501
|
+
break;
|
|
502
|
+
case "TrackEndEvent":
|
|
503
|
+
this.trackEnd(player, player.queue.current, payload);
|
|
504
|
+
break;
|
|
505
|
+
case "TrackStuckEvent":
|
|
506
|
+
this.trackStuck(player, player.queue.current, payload);
|
|
507
|
+
break;
|
|
508
|
+
case "TrackExceptionEvent":
|
|
509
|
+
this.trackError(player, player.queue.current, payload);
|
|
510
|
+
break;
|
|
511
|
+
case "WebSocketClosedEvent":
|
|
512
|
+
this.socketClosed(player, payload);
|
|
513
|
+
break;
|
|
514
|
+
default:
|
|
515
|
+
this.NodeManager.emit("error", this, new Error(`Node#event unknown event '${payload.type}'.`), payload);
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
trackStart(player, track, payload) {
|
|
521
|
+
player.playing = true;
|
|
522
|
+
player.paused = false;
|
|
523
|
+
return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
|
|
524
|
+
}
|
|
525
|
+
async trackEnd(player, track, payload) {
|
|
526
|
+
// If there are no songs in the queue
|
|
527
|
+
if (!player.queue.tracks.length && player.repeatMode === "off")
|
|
528
|
+
return this.queueEnd(player, track, payload);
|
|
529
|
+
// If a track was forcibly played
|
|
530
|
+
if (payload.reason === "replaced")
|
|
531
|
+
return this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
|
|
532
|
+
// If a track had an error while starting
|
|
533
|
+
if (["loadFailed", "cleanup"].includes(payload.reason)) {
|
|
534
|
+
await (0, Utils_1.queueTrackEnd)(player.queue);
|
|
535
|
+
// if no track available, end queue
|
|
536
|
+
if (!player.queue.current)
|
|
537
|
+
return this.queueEnd(player, track, payload);
|
|
538
|
+
// fire event
|
|
539
|
+
this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
|
|
540
|
+
// play track if autoSkip is true
|
|
541
|
+
return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ track: player.queue.current, noReplace: true });
|
|
542
|
+
}
|
|
543
|
+
// remove tracks from the queue
|
|
544
|
+
if (player.repeatMode !== "track")
|
|
545
|
+
await (0, Utils_1.queueTrackEnd)(player.queue, player.repeatMode === "queue");
|
|
546
|
+
// if no track available, end queue
|
|
547
|
+
if (!player.queue.current)
|
|
548
|
+
return this.queueEnd(player, track, payload);
|
|
549
|
+
// fire event
|
|
550
|
+
this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
|
|
551
|
+
// play track if autoSkip is true
|
|
552
|
+
return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ track: player.queue.current, noReplace: true });
|
|
553
|
+
}
|
|
554
|
+
async queueEnd(player, track, payload) {
|
|
555
|
+
player.queue.current = null;
|
|
556
|
+
player.playing = false;
|
|
557
|
+
if (payload?.reason !== "stopped") {
|
|
558
|
+
await player.queue.utils.save();
|
|
559
|
+
}
|
|
560
|
+
if (typeof this.NodeManager.LavalinkManager.options.playerOptions?.onEmptyQueue?.destroyAfterMs === "number" && !isNaN(this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs) && this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs >= 0) {
|
|
561
|
+
if (this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs === 0)
|
|
562
|
+
return player.destroy(Player_1.DestroyReasons.QueueEmpty);
|
|
563
|
+
else {
|
|
564
|
+
if (player.get("internal_queueempty")) {
|
|
565
|
+
clearTimeout(player.get("internal_queueempty"));
|
|
566
|
+
player.set("internal_queueempty", undefined);
|
|
567
|
+
}
|
|
568
|
+
player.set("internal_queueempty", setTimeout(() => {
|
|
569
|
+
player.destroy(Player_1.DestroyReasons.QueueEmpty);
|
|
570
|
+
}, this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return this.NodeManager.LavalinkManager.emit("queueEnd", player, track, payload);
|
|
574
|
+
}
|
|
575
|
+
async trackStuck(player, track, payload) {
|
|
576
|
+
this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
|
|
577
|
+
// If there are no songs in the queue
|
|
578
|
+
if (!player.queue.tracks.length && player.repeatMode === "off")
|
|
579
|
+
return;
|
|
580
|
+
// remove the current track, and enqueue the next one
|
|
581
|
+
await (0, Utils_1.queueTrackEnd)(player.queue, player.repeatMode === "queue");
|
|
582
|
+
// if no track available, end queue
|
|
583
|
+
if (!player.queue.current)
|
|
584
|
+
return this.queueEnd(player, track, payload);
|
|
585
|
+
// play track if autoSkip is true
|
|
586
|
+
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ track: player.queue.current, noReplace: true });
|
|
587
|
+
}
|
|
588
|
+
async trackError(player, track, payload) {
|
|
589
|
+
this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
|
|
590
|
+
// remove the current track, and enqueue the next one
|
|
591
|
+
await (0, Utils_1.queueTrackEnd)(player.queue, player.repeatMode === "queue");
|
|
592
|
+
// if no track available, end queue
|
|
593
|
+
if (!player.queue.current)
|
|
594
|
+
return this.queueEnd(player, track, payload);
|
|
595
|
+
// play track if autoSkip is true
|
|
596
|
+
return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ track: player.queue.current, noReplace: true });
|
|
597
|
+
}
|
|
598
|
+
socketClosed(player, payload) {
|
|
599
|
+
return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
exports.LavalinkNode = LavalinkNode;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from "stream";
|
|
3
|
+
import { LavalinkNode, LavalinkNodeOptions } from "./Node";
|
|
4
|
+
import { LavalinkManager } from "./LavalinkManager";
|
|
5
|
+
import { MiniMap } from "./Utils";
|
|
6
|
+
import { DestroyReasonsType } from "./Player";
|
|
7
|
+
type LavalinkNodeIdentifier = string;
|
|
8
|
+
interface NodeManagerEvents {
|
|
9
|
+
/**
|
|
10
|
+
* Emitted when a Node is created.
|
|
11
|
+
* @event Manager.nodeManager#create
|
|
12
|
+
*/
|
|
13
|
+
"create": (node: LavalinkNode) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Emitted when a Node is destroyed.
|
|
16
|
+
* @event Manager.nodeManager#destroy
|
|
17
|
+
*/
|
|
18
|
+
"destroy": (node: LavalinkNode, destroyReason?: DestroyReasonsType) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Emitted when a Node is connected.
|
|
21
|
+
* @event Manager.nodeManager#connect
|
|
22
|
+
*/
|
|
23
|
+
"connect": (node: LavalinkNode) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Emitted when a Node is reconnecting.
|
|
26
|
+
* @event Manager.nodeManager#reconnecting
|
|
27
|
+
*/
|
|
28
|
+
"reconnecting": (node: LavalinkNode) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Emitted when a Node is disconnects.
|
|
31
|
+
* @event Manager.nodeManager#disconnect
|
|
32
|
+
*/
|
|
33
|
+
"disconnect": (node: LavalinkNode, reason: {
|
|
34
|
+
code?: number;
|
|
35
|
+
reason?: string;
|
|
36
|
+
}) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Emitted when a Node is error.
|
|
39
|
+
* @event Manager.nodeManager#error
|
|
40
|
+
*/
|
|
41
|
+
"error": (node: LavalinkNode, error: Error, payload?: unknown) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Emits every single Node event.
|
|
44
|
+
* @event Manager.nodeManager#raw
|
|
45
|
+
*/
|
|
46
|
+
"raw": (node: LavalinkNode, payload: unknown) => void;
|
|
47
|
+
}
|
|
48
|
+
export declare interface NodeManager {
|
|
49
|
+
on<U extends keyof NodeManagerEvents>(event: U, listener: NodeManagerEvents[U]): this;
|
|
50
|
+
emit<U extends keyof NodeManagerEvents>(event: U, ...args: Parameters<NodeManagerEvents[U]>): boolean;
|
|
51
|
+
/** @private */
|
|
52
|
+
LavalinkManager: LavalinkManager;
|
|
53
|
+
}
|
|
54
|
+
export declare class NodeManager extends EventEmitter {
|
|
55
|
+
nodes: MiniMap<string, LavalinkNode>;
|
|
56
|
+
constructor(LavalinkManager: LavalinkManager);
|
|
57
|
+
createNode(options: LavalinkNodeOptions): LavalinkNode;
|
|
58
|
+
get leastUsedNodes(): LavalinkNode[];
|
|
59
|
+
deleteNode(node: LavalinkNodeIdentifier | LavalinkNode): void;
|
|
60
|
+
}
|
|
61
|
+
export {};
|