magmastream 2.9.0-dev.4 → 2.9.0-dev.41

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.
@@ -1,76 +1,70 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Node = exports.SponsorBlockSegment = void 0;
3
+ exports.Node = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const Utils_1 = require("./Utils");
6
- const Manager_1 = require("./Manager");
7
6
  const Rest_1 = require("./Rest");
8
7
  const nodeCheck_1 = tslib_1.__importDefault(require("../utils/nodeCheck"));
9
8
  const ws_1 = tslib_1.__importDefault(require("ws"));
10
9
  const fs_1 = tslib_1.__importDefault(require("fs"));
11
10
  const path_1 = tslib_1.__importDefault(require("path"));
12
- var SponsorBlockSegment;
13
- (function (SponsorBlockSegment) {
14
- SponsorBlockSegment["Sponsor"] = "sponsor";
15
- SponsorBlockSegment["SelfPromo"] = "selfpromo";
16
- SponsorBlockSegment["Interaction"] = "interaction";
17
- SponsorBlockSegment["Intro"] = "intro";
18
- SponsorBlockSegment["Outro"] = "outro";
19
- SponsorBlockSegment["Preview"] = "preview";
20
- SponsorBlockSegment["MusicOfftopic"] = "music_offtopic";
21
- SponsorBlockSegment["Filler"] = "filler";
22
- })(SponsorBlockSegment || (exports.SponsorBlockSegment = SponsorBlockSegment = {}));
23
- const validSponsorBlocks = Object.values(SponsorBlockSegment).map((v) => v.toLowerCase());
24
- const sessionIdsFilePath = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "sessionIds.json");
25
- let sessionIdsMap = new Map();
26
- const configDir = path_1.default.dirname(sessionIdsFilePath);
27
- if (!fs_1.default.existsSync(configDir)) {
28
- fs_1.default.mkdirSync(configDir, { recursive: true });
29
- }
11
+ const Enums_1 = require("./Enums");
12
+ const validSponsorBlocks = Object.values(Enums_1.SponsorBlockSegment).map((v) => v.toLowerCase());
30
13
  class Node {
14
+ manager;
31
15
  options;
32
16
  /** The socket for the node. */
33
17
  socket = null;
34
18
  /** The stats for the node. */
35
19
  stats;
36
20
  /** The manager for the node */
37
- manager;
21
+ // public manager: Manager;
38
22
  /** The node's session ID. */
39
23
  sessionId;
40
24
  /** The REST instance. */
41
25
  rest;
42
26
  /** Actual Lavalink information of the node. */
43
27
  info = null;
44
- static _manager;
28
+ /** Whether the node is a NodeLink. */
29
+ isNodeLink = false;
45
30
  reconnectTimeout;
46
31
  reconnectAttempts = 1;
32
+ redisPrefix;
33
+ sessionIdsFilePath;
34
+ sessionIdsMap = new Map();
47
35
  /**
48
36
  * Creates an instance of Node.
49
- * @param options
37
+ * @param manager - The manager for the node.
38
+ * @param options - The options for the node.
50
39
  */
51
- constructor(options) {
40
+ constructor(manager, options) {
41
+ this.manager = manager;
52
42
  this.options = options;
53
43
  if (!this.manager)
54
- this.manager = Utils_1.Structure.get("Node")._manager;
55
- if (!this.manager)
56
- throw new RangeError("Manager has not been initiated.");
44
+ throw new RangeError("Manager instance is required.");
57
45
  if (this.manager.nodes.has(options.identifier || options.host)) {
58
46
  return this.manager.nodes.get(options.identifier || options.host);
59
47
  }
60
48
  (0, nodeCheck_1.default)(options);
61
49
  this.options = {
62
- port: 2333,
63
- password: "youshallnotpass",
64
- useSSL: false,
65
- maxRetryAttempts: 30,
66
- retryDelayMs: 60000,
67
- nodePriority: 0,
68
50
  ...options,
51
+ host: options.host ?? "localhost",
52
+ port: options.port ?? 2333,
53
+ password: options.password ?? "youshallnotpass",
54
+ useSSL: options.useSSL ?? false,
55
+ identifier: options.identifier ?? options.host,
56
+ maxRetryAttempts: options.maxRetryAttempts ?? 30,
57
+ retryDelayMs: options.retryDelayMs ?? 60000,
58
+ enableSessionResumeOption: options.enableSessionResumeOption ?? false,
59
+ sessionTimeoutSeconds: options.sessionTimeoutSeconds ?? 60,
60
+ apiRequestTimeoutMs: options.apiRequestTimeoutMs ?? 10000,
61
+ nodePriority: options.nodePriority ?? 0,
62
+ isNodeLink: options.isNodeLink ?? false,
63
+ isBackup: options.isBackup ?? false,
69
64
  };
70
65
  if (this.options.useSSL) {
71
66
  this.options.port = 443;
72
67
  }
73
- this.options.identifier = options.identifier || options.host;
74
68
  this.stats = {
75
69
  players: 0,
76
70
  playingPlayers: 0,
@@ -93,38 +87,47 @@ class Node {
93
87
  },
94
88
  };
95
89
  this.manager.nodes.set(this.options.identifier, this);
96
- this.manager.emit(Manager_1.ManagerEventTypes.NodeCreate, this);
90
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeCreate, this);
97
91
  this.rest = new Rest_1.Rest(this, this.manager);
98
- this.createSessionIdsFile();
99
- this.loadSessionIds();
100
- // Create README file to inform the user about the magmastream folder
101
- this.createReadmeFile();
92
+ switch (this.manager.options.stateStorage.type) {
93
+ case Enums_1.StateStorageType.JSON:
94
+ this.sessionIdsFilePath = path_1.default.join(process.cwd(), "magmastream", "dist", "sessionData", "sessionIds.json");
95
+ const configDir = path_1.default.dirname(this.sessionIdsFilePath);
96
+ if (!fs_1.default.existsSync(configDir)) {
97
+ fs_1.default.mkdirSync(configDir, { recursive: true });
98
+ }
99
+ this.createSessionIdsFile();
100
+ this.createReadmeFile();
101
+ break;
102
+ case Enums_1.StateStorageType.Redis:
103
+ this.redisPrefix = this.manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
104
+ ? this.manager.options.stateStorage.redisConfig.prefix
105
+ : this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:";
106
+ break;
107
+ }
102
108
  }
103
- /** Returns if connected to the Node. */
109
+ /**
110
+ * Checks if the Node is currently connected.
111
+ * This method returns true if the Node has an active WebSocket connection, indicating it is ready to receive and process commands.
112
+ */
104
113
  get connected() {
105
114
  if (!this.socket)
106
115
  return false;
107
116
  return this.socket.readyState === ws_1.default.OPEN;
108
117
  }
109
- /** Returns the address for this node. */
118
+ /** Returns the full address for this node, including the host and port. */
110
119
  get address() {
111
120
  return `${this.options.host}:${this.options.port}`;
112
121
  }
113
- /** @hidden */
114
- static init(manager) {
115
- this._manager = manager;
116
- }
117
122
  /**
118
123
  * Creates the sessionIds.json file if it doesn't exist. This file is used to
119
124
  * store the session IDs for each node. The session IDs are used to identify
120
125
  * the node when resuming a session.
121
126
  */
122
127
  createSessionIdsFile() {
123
- // If the sessionIds.json file does not exist, create it
124
- if (!fs_1.default.existsSync(sessionIdsFilePath)) {
125
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Creating sessionId file at: ${sessionIdsFilePath}`);
126
- // Create the file with an empty object as the content
127
- fs_1.default.writeFileSync(sessionIdsFilePath, JSON.stringify({}), "utf-8");
128
+ if (!fs_1.default.existsSync(this.sessionIdsFilePath)) {
129
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Creating sessionId file at: ${this.sessionIdsFilePath}`);
130
+ fs_1.default.writeFileSync(this.sessionIdsFilePath, JSON.stringify({}), "utf-8");
128
131
  }
129
132
  }
130
133
  /**
@@ -135,21 +138,48 @@ class Node {
135
138
  * of the node identifier and cluster ID. This allows multiple clusters to
136
139
  * be used with the same node identifier.
137
140
  */
138
- loadSessionIds() {
139
- // Check if the sessionIds.json file exists
140
- if (fs_1.default.existsSync(sessionIdsFilePath)) {
141
- // Emit a debug event indicating that session IDs are being loaded
142
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from file: ${sessionIdsFilePath}`);
143
- // Read the content of the sessionIds.json file as a string
144
- const sessionIdsData = fs_1.default.readFileSync(sessionIdsFilePath, "utf-8");
145
- // Parse the JSON string into an object and convert it into a Map
146
- sessionIdsMap = new Map(Object.entries(JSON.parse(sessionIdsData)));
147
- // Check if the session IDs Map contains the session ID for this node
148
- const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
149
- if (sessionIdsMap.has(compositeKey)) {
150
- // Set the session ID on this node if it exists in the session IDs Map
151
- this.sessionId = sessionIdsMap.get(compositeKey);
141
+ async loadSessionIds() {
142
+ switch (this.manager.options.stateStorage.type) {
143
+ case Enums_1.StateStorageType.JSON: {
144
+ if (fs_1.default.existsSync(this.sessionIdsFilePath)) {
145
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from file: ${this.sessionIdsFilePath}`);
146
+ const sessionIdsData = fs_1.default.readFileSync(this.sessionIdsFilePath, "utf-8");
147
+ this.sessionIdsMap = new Map(Object.entries(JSON.parse(sessionIdsData)));
148
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
149
+ if (this.sessionIdsMap.has(compositeKey)) {
150
+ this.sessionId = this.sessionIdsMap.get(compositeKey);
151
+ }
152
+ }
153
+ break;
152
154
  }
155
+ case Enums_1.StateStorageType.Redis:
156
+ const key = `${this.redisPrefix}node:sessionIds`;
157
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from Redis key: ${key}`);
158
+ const currentRaw = await this.manager.redis.get(key);
159
+ if (currentRaw) {
160
+ try {
161
+ const sessionIds = JSON.parse(currentRaw);
162
+ if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
163
+ throw new Error("[NODE] loadSessionIds invalid data type from Redis.");
164
+ }
165
+ this.sessionIdsMap = new Map(Object.entries(sessionIds));
166
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
167
+ if (this.sessionIdsMap.has(compositeKey)) {
168
+ this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
169
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
170
+ }
171
+ }
172
+ catch (err) {
173
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to parse Redis sessionIds: ${err.message}`);
174
+ this.sessionIdsMap = new Map();
175
+ }
176
+ }
177
+ else {
178
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] No sessionIds found in Redis — creating new key.`);
179
+ await this.manager.redis.set(key, JSON.stringify({}));
180
+ this.sessionIdsMap = new Map();
181
+ }
182
+ break;
153
183
  }
154
184
  }
155
185
  /**
@@ -163,15 +193,43 @@ class Node {
163
193
  * of the node identifier and cluster ID. This allows multiple clusters to
164
194
  * be used with the same node identifier.
165
195
  */
166
- updateSessionId() {
167
- // Emit a debug event indicating that the session IDs are being updated
168
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds to file: ${sessionIdsFilePath}`);
169
- // Create a composite key using identifier and clusterId
170
- const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
171
- // Update the session IDs Map with the new session ID
172
- sessionIdsMap.set(compositeKey, this.sessionId);
173
- // Write the updated session IDs Map to the sessionIds.json file
174
- fs_1.default.writeFileSync(sessionIdsFilePath, JSON.stringify(Object.fromEntries(sessionIdsMap)));
196
+ async updateSessionId() {
197
+ switch (this.manager.options.stateStorage.type) {
198
+ case Enums_1.StateStorageType.JSON: {
199
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds to file: ${this.sessionIdsFilePath}`);
200
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
201
+ this.sessionIdsMap.set(compositeKey, this.sessionId);
202
+ fs_1.default.writeFileSync(this.sessionIdsFilePath, JSON.stringify(Object.fromEntries(this.sessionIdsMap)));
203
+ break;
204
+ }
205
+ case Enums_1.StateStorageType.Redis: {
206
+ const key = `${this.redisPrefix}node:sessionIds`;
207
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
208
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds in Redis key: ${key}`);
209
+ const currentRaw = await this.manager.redis.get(key);
210
+ let sessionIds;
211
+ if (currentRaw) {
212
+ try {
213
+ sessionIds = JSON.parse(currentRaw);
214
+ if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
215
+ throw new Error("Invalid data type in Redis");
216
+ }
217
+ }
218
+ catch (err) {
219
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Corrupted Redis sessionIds, reinitializing: ${err.message}`);
220
+ sessionIds = {};
221
+ }
222
+ }
223
+ else {
224
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Redis key not found — creating new sessionIds key.`);
225
+ sessionIds = {};
226
+ }
227
+ sessionIds[compositeKey] = this.sessionId;
228
+ this.sessionIdsMap = new Map(Object.entries(sessionIds));
229
+ await this.manager.redis.set(key, JSON.stringify(sessionIds));
230
+ break;
231
+ }
232
+ }
175
233
  }
176
234
  /**
177
235
  * Connects to the Node.
@@ -182,7 +240,8 @@ class Node {
182
240
  * If the node has no session ID but the `enableSessionResumeOption` option is true, it will use the session ID
183
241
  * stored in the sessionIds.json file if it exists.
184
242
  */
185
- connect() {
243
+ async connect() {
244
+ await this.loadSessionIds();
186
245
  if (this.connected)
187
246
  return;
188
247
  const headers = {
@@ -194,13 +253,14 @@ class Node {
194
253
  if (this.sessionId) {
195
254
  headers["Session-Id"] = this.sessionId;
196
255
  }
197
- else if (this.options.enableSessionResumeOption && sessionIdsMap.has(compositeKey)) {
198
- this.sessionId = sessionIdsMap.get(compositeKey) || null;
256
+ else if (this.options.enableSessionResumeOption && this.sessionIdsMap.has(compositeKey)) {
257
+ this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
199
258
  headers["Session-Id"] = this.sessionId;
200
259
  }
201
260
  this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
202
261
  this.socket.on("open", this.open.bind(this));
203
262
  this.socket.on("close", this.close.bind(this));
263
+ this.socket.on("upgrade", (request) => this.upgrade(request));
204
264
  this.socket.on("message", this.message.bind(this));
205
265
  this.socket.on("error", this.error.bind(this));
206
266
  const debugInfo = {
@@ -214,7 +274,7 @@ class Node {
214
274
  identifier: this.options.identifier,
215
275
  },
216
276
  };
217
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Connecting ${JSON.stringify(debugInfo)}`);
277
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Connecting ${JSON.stringify(debugInfo)}`);
218
278
  }
219
279
  /**
220
280
  * Destroys the node and cleans up associated resources.
@@ -229,7 +289,6 @@ class Node {
229
289
  async destroy() {
230
290
  if (!this.connected)
231
291
  return;
232
- // Emit a debug event indicating that the node is being destroyed
233
292
  const debugInfo = {
234
293
  connected: this.connected,
235
294
  identifier: this.options.identifier,
@@ -237,25 +296,20 @@ class Node {
237
296
  sessionId: this.sessionId,
238
297
  playerCount: this.manager.players.filter((p) => p.node == this).size,
239
298
  };
240
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Destroying node: ${JSON.stringify(debugInfo)}`);
299
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Destroying node: ${JSON.stringify(debugInfo)}`);
241
300
  // Automove all players connected to that node
242
301
  const players = this.manager.players.filter((p) => p.node == this);
243
302
  if (players.size) {
244
- players.forEach(async (player) => {
303
+ for (const player of players.values()) {
245
304
  await player.autoMoveNode();
246
- });
305
+ }
247
306
  }
248
- // Close the WebSocket connection
249
307
  this.socket.close(1000, "destroy");
250
- // Remove all event listeners on the WebSocket
251
308
  this.socket.removeAllListeners();
252
- // Clear the reconnect timeout
253
309
  this.reconnectAttempts = 1;
254
310
  clearTimeout(this.reconnectTimeout);
255
- // Emit a "nodeDestroy" event with the node as the argument
256
- this.manager.emit(Manager_1.ManagerEventTypes.NodeDestroy, this);
257
- // Destroy the node from the manager
258
- await this.manager.destroyNode(this.options.identifier);
311
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeDestroy, this);
312
+ this.manager.nodes.delete(this.options.identifier);
259
313
  }
260
314
  /**
261
315
  * Attempts to reconnect to the node if the connection is lost.
@@ -274,7 +328,6 @@ class Node {
274
328
  * @emits {nodeDestroy} - Emits a nodeDestroy event if the maximum number of retry attempts is reached.
275
329
  */
276
330
  async reconnect() {
277
- // Collect debug information regarding the current state of the node
278
331
  const debugInfo = {
279
332
  identifier: this.options.identifier,
280
333
  connected: this.connected,
@@ -282,27 +335,28 @@ class Node {
282
335
  maxRetryAttempts: this.options.maxRetryAttempts,
283
336
  retryDelayMs: this.options.retryDelayMs,
284
337
  };
285
- // Emit a debug event indicating the node is attempting to reconnect
286
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Reconnecting node: ${JSON.stringify(debugInfo)}`);
287
- // Schedule the reconnection attempt after the specified retry delay
338
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Reconnecting node: ${JSON.stringify(debugInfo)}`);
288
339
  this.reconnectTimeout = setTimeout(async () => {
289
- // Check if the maximum number of retry attempts has been reached
290
340
  if (this.reconnectAttempts >= this.options.maxRetryAttempts) {
291
- // Emit an error event and destroy the node if retries are exhausted
292
341
  const error = new Error(`Unable to connect after ${this.options.maxRetryAttempts} attempts.`);
293
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, error);
342
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
294
343
  return await this.destroy();
295
344
  }
296
- // Remove all listeners from the current WebSocket and reset it
297
345
  this.socket?.removeAllListeners();
298
346
  this.socket = null;
299
- // Emit a nodeReconnect event and attempt to connect again
300
- this.manager.emit(Manager_1.ManagerEventTypes.NodeReconnect, this);
347
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeReconnect, this);
301
348
  this.connect();
302
- // Increment the reconnect attempts counter
303
349
  this.reconnectAttempts++;
304
350
  }, this.options.retryDelayMs);
305
351
  }
352
+ /**
353
+ * Upgrades the node to a NodeLink.
354
+ *
355
+ * @param request - The incoming message.
356
+ */
357
+ upgrade(request) {
358
+ this.isNodeLink = this.options.isNodeLink ?? Boolean(request.headers.isnodelink) ?? false;
359
+ }
306
360
  /**
307
361
  * Handles the "open" event emitted by the WebSocket connection.
308
362
  *
@@ -312,18 +366,18 @@ class Node {
312
366
  * with the node as the argument.
313
367
  */
314
368
  open() {
315
- // Clear any existing reconnect timeouts
316
369
  if (this.reconnectTimeout)
317
370
  clearTimeout(this.reconnectTimeout);
318
- // Collect debug information regarding the current state of the node
319
371
  const debugInfo = {
320
372
  identifier: this.options.identifier,
321
373
  connected: this.connected,
322
374
  };
323
- // Emit a debug event indicating the node is connected
324
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Connected node: ${JSON.stringify(debugInfo)}`);
325
- // Emit a "nodeConnect" event with the node as the argument
326
- this.manager.emit(Manager_1.ManagerEventTypes.NodeConnect, this);
375
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Connected node: ${JSON.stringify(debugInfo)}`);
376
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeConnect, this);
377
+ const playersOnBackupNode = this.manager.players.filter((p) => p.node.options.isBackup);
378
+ if (playersOnBackupNode.size) {
379
+ Promise.all(Array.from(playersOnBackupNode.values(), (player) => player.moveNode(this.options.identifier)));
380
+ }
327
381
  }
328
382
  /**
329
383
  * Handles the "close" event emitted by the WebSocket connection.
@@ -344,18 +398,21 @@ class Node {
344
398
  code,
345
399
  reason,
346
400
  };
347
- // Emit a "nodeDisconnect" event with the node and the close event as arguments
348
- this.manager.emit(Manager_1.ManagerEventTypes.NodeDisconnect, this, { code, reason });
349
- // Emit a debug event indicating the node is disconnected
350
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Disconnected node: ${JSON.stringify(debugInfo)}`);
351
- // Try moving all players connected to that node to a useable one
401
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeDisconnect, this, { code, reason });
402
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Disconnected node: ${JSON.stringify(debugInfo)}`);
352
403
  if (this.manager.useableNode) {
353
404
  const players = this.manager.players.filter((p) => p.node.options.identifier == this.options.identifier);
354
405
  if (players.size) {
355
406
  await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
356
407
  }
357
408
  }
358
- // If the close event was not initiated by the user, attempt to reconnect
409
+ else {
410
+ const backUpNodes = this.manager.nodes.filter((node) => node.options.isBackup && node.connected);
411
+ const backupNode = backUpNodes.first();
412
+ if (backupNode) {
413
+ await Promise.all(Array.from(this.manager.players.values(), (player) => player.moveNode(backupNode.options.identifier)));
414
+ }
415
+ }
359
416
  if (code !== 1000 || reason !== "destroy")
360
417
  await this.reconnect();
361
418
  }
@@ -370,15 +427,12 @@ class Node {
370
427
  error(error) {
371
428
  if (!error)
372
429
  return;
373
- // Collect debug information regarding the error
374
430
  const debugInfo = {
375
431
  identifier: this.options.identifier,
376
432
  error: error.message,
377
433
  };
378
- // Emit a debug event indicating the error on the node
379
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Error on node: ${JSON.stringify(debugInfo)}`);
380
- // Emit a "nodeError" event with the node and the error as arguments
381
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, error);
434
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Error on node: ${JSON.stringify(debugInfo)}`);
435
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
382
436
  }
383
437
  /**
384
438
  * Handles incoming messages from the Lavalink WebSocket connection.
@@ -397,7 +451,7 @@ class Node {
397
451
  const payload = JSON.parse(d.toString());
398
452
  if (!payload.op)
399
453
  return;
400
- this.manager.emit(Manager_1.ManagerEventTypes.NodeRaw, payload);
454
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeRaw, payload);
401
455
  let player;
402
456
  switch (payload.op) {
403
457
  case "stats":
@@ -417,29 +471,27 @@ class Node {
417
471
  if (player && player.node.options.identifier !== this.options.identifier) {
418
472
  return;
419
473
  }
420
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
474
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
421
475
  await this.handleEvent(payload);
422
476
  break;
423
477
  case "ready":
424
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
478
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
425
479
  this.rest.setSessionId(payload.sessionId);
426
480
  this.sessionId = payload.sessionId;
427
- this.updateSessionId(); // Call to update session ID
481
+ await this.updateSessionId();
428
482
  this.info = await this.fetchInfo();
429
- // Log if the session was resumed successfully
430
483
  if (payload.resumed) {
431
- // Load player states from the JSON file
432
484
  await this.manager.loadPlayerStates(this.options.identifier);
433
485
  }
434
486
  if (this.options.enableSessionResumeOption) {
435
487
  await this.rest.patch(`/v4/sessions/${this.sessionId}`, {
436
488
  resuming: this.options.enableSessionResumeOption,
437
- timeout: this.options.sessionTimeoutMs,
489
+ timeout: this.options.sessionTimeoutSeconds,
438
490
  });
439
491
  }
440
492
  break;
441
493
  default:
442
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected op "${payload.op}" with data: ${payload.message}`));
494
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected op "${payload.op}" with data: ${payload.message}`));
443
495
  return;
444
496
  }
445
497
  }
@@ -455,7 +507,7 @@ class Node {
455
507
  const player = this.manager.players.get(payload.guildId);
456
508
  if (!player)
457
509
  return;
458
- const track = player.queue.current;
510
+ const track = await player.queue.getCurrent();
459
511
  const type = payload.type;
460
512
  let error;
461
513
  switch (type) {
@@ -478,20 +530,29 @@ class Node {
478
530
  this.socketClosed(player, payload);
479
531
  break;
480
532
  case "SegmentsLoaded":
481
- this.sponsorBlockSegmentLoaded(player, player.queue.current, payload);
533
+ this.sponsorBlockSegmentLoaded(player, track, payload);
482
534
  break;
483
535
  case "SegmentSkipped":
484
- this.sponsorBlockSegmentSkipped(player, player.queue.current, payload);
536
+ this.sponsorBlockSegmentSkipped(player, track, payload);
485
537
  break;
486
538
  case "ChaptersLoaded":
487
- this.sponsorBlockChaptersLoaded(player, player.queue.current, payload);
539
+ this.sponsorBlockChaptersLoaded(player, track, payload);
488
540
  break;
489
541
  case "ChapterStarted":
490
- this.sponsorBlockChapterStarted(player, player.queue.current, payload);
542
+ this.sponsorBlockChapterStarted(player, track, payload);
543
+ break;
544
+ case "LyricsFoundEvent":
545
+ this.lyricsFound(player, track, payload);
546
+ break;
547
+ case "LyricsNotFoundEvent":
548
+ this.lyricsNotFound(player, track, payload);
549
+ break;
550
+ case "LyricsLineEvent":
551
+ this.lyricsLine(player, track, payload);
491
552
  break;
492
553
  default:
493
554
  error = new Error(`Node#event unknown event '${type}'.`);
494
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, error);
555
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
495
556
  break;
496
557
  }
497
558
  }
@@ -506,22 +567,24 @@ class Node {
506
567
  const oldPlayer = player;
507
568
  player.playing = true;
508
569
  player.paused = false;
509
- this.manager.emit(Manager_1.ManagerEventTypes.TrackStart, player, track, payload);
570
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackStart, player, track, payload);
510
571
  const botUser = player.get("Internal_BotUser");
511
572
  if (botUser && botUser.id === track.requester.id) {
512
- this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
513
- changeType: Manager_1.PlayerStateEventTypes.TrackChange,
573
+ this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
574
+ changeType: Enums_1.PlayerStateEventTypes.TrackChange,
514
575
  details: {
515
- changeType: "autoPlay",
576
+ type: "track",
577
+ action: "autoPlay",
516
578
  track: track,
517
579
  },
518
580
  });
519
581
  return;
520
582
  }
521
- this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
522
- changeType: Manager_1.PlayerStateEventTypes.TrackChange,
583
+ this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
584
+ changeType: Enums_1.PlayerStateEventTypes.TrackChange,
523
585
  details: {
524
- changeType: "start",
586
+ type: "track",
587
+ action: "start",
525
588
  track: track,
526
589
  },
527
590
  });
@@ -536,42 +599,41 @@ class Node {
536
599
  async trackEnd(player, track, payload) {
537
600
  const { reason } = payload;
538
601
  const skipFlag = player.get("skipFlag");
539
- if (!skipFlag && (player.queue.previous.length === 0 || (player.queue.previous[0] && player.queue.previous[0].track !== player.queue.current?.track))) {
540
- // Store the current track in the previous tracks queue
541
- player.queue.previous.push(player.queue.current);
542
- // Limit the previous tracks queue to maxPreviousTracks
543
- if (player.queue.previous.length > this.manager.options.maxPreviousTracks) {
544
- player.queue.previous.shift();
602
+ const previous = await player.queue.getPrevious();
603
+ const current = await player.queue.getCurrent();
604
+ if (!skipFlag && (previous.length === 0 || (previous[0] && previous[0].track !== current?.track))) {
605
+ await player.queue.addPrevious(current);
606
+ const updated = await player.queue.getPrevious();
607
+ if (updated.length > this.manager.options.maxPreviousTracks) {
608
+ const trimmed = updated.slice(0, this.manager.options.maxPreviousTracks);
609
+ await player.queue.setPrevious(trimmed);
545
610
  }
546
611
  }
612
+ player.set("skipFlag", false);
547
613
  const oldPlayer = player;
548
- // Handle track end events
549
614
  switch (reason) {
550
- case Utils_1.TrackEndReasonTypes.LoadFailed:
551
- case Utils_1.TrackEndReasonTypes.Cleanup:
552
- // Handle the case when a track failed to load or was cleaned up
615
+ case Enums_1.TrackEndReasonTypes.LoadFailed:
616
+ case Enums_1.TrackEndReasonTypes.Cleanup:
553
617
  await this.handleFailedTrack(player, track, payload);
554
618
  break;
555
- case Utils_1.TrackEndReasonTypes.Replaced:
556
- // Handle the case when a track was replaced
619
+ case Enums_1.TrackEndReasonTypes.Replaced:
557
620
  break;
558
- case Utils_1.TrackEndReasonTypes.Stopped:
559
- // If the track was forcibly replaced
560
- if (player.queue.length) {
621
+ case Enums_1.TrackEndReasonTypes.Stopped:
622
+ if (await player.queue.size()) {
561
623
  await this.playNextTrack(player, track, payload);
562
624
  }
563
625
  else {
564
626
  await this.queueEnd(player, track, payload);
565
627
  }
566
628
  break;
567
- case Utils_1.TrackEndReasonTypes.Finished:
629
+ case Enums_1.TrackEndReasonTypes.Finished:
568
630
  // If the track ended and it's set to repeat (track or queue)
569
631
  if (track && (player.trackRepeat || player.queueRepeat)) {
570
632
  await this.handleRepeatedTrack(player, track, payload);
571
633
  break;
572
634
  }
573
635
  // If there's another track in the queue
574
- if (player.queue.length) {
636
+ if (await player.queue.size()) {
575
637
  await this.playNextTrack(player, track, payload);
576
638
  }
577
639
  else {
@@ -579,13 +641,14 @@ class Node {
579
641
  }
580
642
  break;
581
643
  default:
582
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected track end reason "${reason}"`));
644
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected track end reason "${reason}"`));
583
645
  break;
584
646
  }
585
- this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
586
- changeType: Manager_1.PlayerStateEventTypes.TrackChange,
647
+ this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
648
+ changeType: Enums_1.PlayerStateEventTypes.TrackChange,
587
649
  details: {
588
- changeType: "end",
650
+ type: "track",
651
+ action: "end",
589
652
  track: track,
590
653
  },
591
654
  });
@@ -602,15 +665,22 @@ class Node {
602
665
  */
603
666
  async handleAutoplay(player, attempt = 0) {
604
667
  // If autoplay is not enabled or all attempts have failed, early exit
605
- if (!player.isAutoplay || attempt > player.autoplayTries || !player.queue.previous.length)
668
+ if (!player.isAutoplay || attempt > player.autoplayTries || !(await player.queue.getPrevious()).length)
606
669
  return false;
607
- const lastTrack = player.queue.previous[player.queue.previous.length - 1];
670
+ const PreviousQueue = await player.queue.getPrevious();
671
+ const lastTrack = PreviousQueue?.at(-1);
608
672
  lastTrack.requester = player.get("Internal_BotUser");
609
673
  if (!lastTrack)
610
674
  return false;
611
675
  const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(lastTrack);
612
- if (tracks.length) {
613
- player.queue.add(tracks[0]);
676
+ const normalize = (str) => str
677
+ .toLowerCase()
678
+ .replace(/\s+/g, "")
679
+ .replace(/[^a-z0-9]/g, "");
680
+ const filteredTracks = tracks.filter((track) => track.identifier !== lastTrack.identifier && track.uri !== lastTrack.uri && normalize(track.title) !== normalize(lastTrack.title));
681
+ if (filteredTracks.length) {
682
+ const randomTrack = filteredTracks[Math.floor(Math.random() * filteredTracks.length)];
683
+ await player.queue.add(randomTrack);
614
684
  await player.play();
615
685
  return true;
616
686
  }
@@ -631,12 +701,12 @@ class Node {
631
701
  * @private
632
702
  */
633
703
  async handleFailedTrack(player, track, payload) {
634
- player.queue.current = player.queue.shift();
635
- if (!player.queue.current) {
704
+ await player.queue.setCurrent(await player.queue.dequeue());
705
+ if (!(await player.queue.getCurrent())) {
636
706
  await this.queueEnd(player, track, payload);
637
707
  return;
638
708
  }
639
- this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
709
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
640
710
  if (this.manager.options.playNextOnEnd)
641
711
  await player.play();
642
712
  }
@@ -657,26 +727,27 @@ class Node {
657
727
  const { playNextOnEnd } = this.manager.options;
658
728
  if (trackRepeat) {
659
729
  // Prevent duplicate repeat insertion
660
- if (queue[0] !== queue.current) {
661
- queue.unshift(queue.current);
730
+ if (queue[0] !== (await queue.getCurrent())) {
731
+ await queue.enqueueFront(await queue.getCurrent());
662
732
  }
663
733
  }
664
734
  else if (queueRepeat) {
665
735
  // Prevent duplicate queue insertion
666
- if (queue[queue.length - 1] !== queue.current) {
667
- queue.add(queue.current);
736
+ if (queue[(await queue.size()) - 1] !== (await queue.getCurrent())) {
737
+ await queue.add(await queue.getCurrent());
668
738
  }
669
739
  }
670
740
  // Move to the next track
671
- queue.current = queue.shift();
672
- // Emit track end event
673
- this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
674
- // If the track was stopped manually and no more tracks exist, end the queue
675
- if (payload.reason === Utils_1.TrackEndReasonTypes.Stopped && !(queue.current = queue.shift())) {
676
- await this.queueEnd(player, track, payload);
677
- return;
741
+ await queue.setCurrent(await queue.dequeue());
742
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
743
+ if (payload.reason === Enums_1.TrackEndReasonTypes.Stopped) {
744
+ const next = await queue.dequeue();
745
+ await queue.setCurrent(next ?? null);
746
+ if (!next) {
747
+ await this.queueEnd(player, track, payload);
748
+ return;
749
+ }
678
750
  }
679
- // If autoplay is enabled, play the next track
680
751
  if (playNextOnEnd)
681
752
  await player.play();
682
753
  }
@@ -693,10 +764,8 @@ class Node {
693
764
  */
694
765
  async playNextTrack(player, track, payload) {
695
766
  // Shift the queue to set the next track as current
696
- player.queue.current = player.queue.shift();
697
- // Emit the track end event
698
- this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
699
- // If autoplay is enabled, play the next track
767
+ await player.queue.setCurrent(await player.queue.dequeue());
768
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
700
769
  if (this.manager.options.playNextOnEnd)
701
770
  await player.play();
702
771
  }
@@ -710,10 +779,10 @@ class Node {
710
779
  * @returns {Promise<void>} A promise that resolves when the queue end processing is complete.
711
780
  */
712
781
  async queueEnd(player, track, payload) {
713
- player.queue.current = null;
782
+ await player.queue.setCurrent(null);
714
783
  if (!player.isAutoplay) {
715
784
  player.playing = false;
716
- this.manager.emit(Manager_1.ManagerEventTypes.QueueEnd, player, track, payload);
785
+ this.manager.emit(Enums_1.ManagerEventTypes.QueueEnd, player, track, payload);
717
786
  return;
718
787
  }
719
788
  let attempt = 1;
@@ -724,24 +793,34 @@ class Node {
724
793
  return;
725
794
  attempt++;
726
795
  }
727
- // If all attempts fail, reset the player state and emit queueEnd
728
796
  player.playing = false;
729
- this.manager.emit(Manager_1.ManagerEventTypes.QueueEnd, player, track, payload);
797
+ this.manager.emit(Enums_1.ManagerEventTypes.QueueEnd, player, track, payload);
730
798
  }
731
799
  /**
732
800
  * Fetches the lyrics of a track from the Lavalink node.
733
- * This method uses the `lavalyrics-plugin` to fetch the lyrics.
734
- * If the plugin is not available, it will throw a RangeError.
801
+ *
802
+ * If the node is a NodeLink, it will use the `NodeLinkGetLyrics` method to fetch the lyrics.
803
+ *
804
+ * Requires the `lavalyrics-plugin` to be present in the Lavalink node.
805
+ * Requires the `lavasrc-plugin` or `java-lyrics-plugin` to be present in the Lavalink node.
735
806
  *
736
807
  * @param {Track} track - The track to fetch the lyrics for.
737
808
  * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
738
- * @returns {Promise<Lyrics>} A promise that resolves with the lyrics data.
809
+ * @param {string} [language="en"] - The language of the lyrics.
810
+ * @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
739
811
  */
740
- async getLyrics(track, skipTrackSource = false) {
741
- if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin"))
742
- throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node: ${this.options.identifier}`);
743
- // Make a GET request to the Lavalink node to fetch the lyrics
744
- // The request includes the track URL and the skipTrackSource parameter
812
+ async getLyrics(track, skipTrackSource = false, language) {
813
+ if (!this.connected)
814
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
815
+ if (this.isNodeLink) {
816
+ return (await this.rest.get(`/v4/loadlyrics?encodedTrack=${encodeURIComponent(track.track)}${language ? `&language=${language}` : ""}`));
817
+ }
818
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin")) {
819
+ throw new RangeError(`The plugin "lavalyrics-plugin" must be present in the lavalink node: ${this.options.identifier}`);
820
+ }
821
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavasrc-plugin" || plugin.name === "java-lyrics-plugin")) {
822
+ throw new RangeError(`One of the following plugins must also be present in the lavalink node: "lavasrc-plugin" or "java-lyrics-plugin" (Node: ${this.options.identifier})`);
823
+ }
745
824
  return ((await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`)) || {
746
825
  source: null,
747
826
  provider: null,
@@ -750,6 +829,42 @@ class Node {
750
829
  plugin: [],
751
830
  });
752
831
  }
832
+ /**
833
+ * Subscribes to lyrics for a player.
834
+ * @param {string} guildId - The ID of the guild to subscribe to lyrics for.
835
+ * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
836
+ * @returns {Promise<unknown>} A promise that resolves when the subscription is complete.
837
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
838
+ */
839
+ async lyricsSubscribe(guildId, skipTrackSource = false) {
840
+ if (!this.connected)
841
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
842
+ if (this.isNodeLink)
843
+ throw new RangeError(`The node is a node link: ${this.options.identifier}`);
844
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin")) {
845
+ throw new RangeError(`The plugin "lavalyrics-plugin" must be present in the lavalink node: ${this.options.identifier}`);
846
+ }
847
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavasrc-plugin" || plugin.name === "java-lyrics-plugin")) {
848
+ throw new RangeError(`One of the following plugins must also be present in the lavalink node: "lavasrc-plugin" or "java-lyrics-plugin" (Node: ${this.options.identifier})`);
849
+ }
850
+ return await this.rest.post(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource}`, {});
851
+ }
852
+ /**
853
+ * Unsubscribes from lyrics for a player.
854
+ * @param {string} guildId - The ID of the guild to unsubscribe from lyrics for.
855
+ * @returns {Promise<unknown>} A promise that resolves when the unsubscription is complete.
856
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
857
+ */
858
+ async lyricsUnsubscribe(guildId) {
859
+ if (!this.connected)
860
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
861
+ if (this.isNodeLink)
862
+ throw new RangeError(`The node is a node link: ${this.options.identifier}`);
863
+ if (!this.info.plugins.some((plugin) => plugin.name === "java-lyrics-plugin")) {
864
+ throw new RangeError(`there is no java-lyrics-plugin available in the lavalink node: ${this.options.identifier}`);
865
+ }
866
+ return await this.rest.delete(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`);
867
+ }
753
868
  /**
754
869
  * Handles the event when a track becomes stuck during playback.
755
870
  * Stops the current track and emits a `trackStuck` event.
@@ -762,7 +877,7 @@ class Node {
762
877
  */
763
878
  async trackStuck(player, track, payload) {
764
879
  await player.stop();
765
- this.manager.emit(Manager_1.ManagerEventTypes.TrackStuck, player, track, payload);
880
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackStuck, player, track, payload);
766
881
  }
767
882
  /**
768
883
  * Handles the event when a track has an error during playback.
@@ -776,7 +891,7 @@ class Node {
776
891
  */
777
892
  async trackError(player, track, payload) {
778
893
  await player.stop();
779
- this.manager.emit(Manager_1.ManagerEventTypes.TrackError, player, track, payload);
894
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackError, player, track, payload);
780
895
  }
781
896
  /**
782
897
  * Emitted when the WebSocket connection for a player closes.
@@ -785,8 +900,8 @@ class Node {
785
900
  * @param {WebSocketClosedEvent} payload - The event payload containing additional data about the WebSocket close event.
786
901
  */
787
902
  socketClosed(player, payload) {
788
- this.manager.emit(Manager_1.ManagerEventTypes.SocketClosed, player, payload);
789
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Websocket closed for player: ${player.guildId} with payload: ${JSON.stringify(payload)}`);
903
+ this.manager.emit(Enums_1.ManagerEventTypes.SocketClosed, player, payload);
904
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Websocket closed for player: ${player.guildId} with payload: ${JSON.stringify(payload)}`);
790
905
  }
791
906
  /**
792
907
  * Emitted when the segments for a track are loaded.
@@ -796,7 +911,7 @@ class Node {
796
911
  * @param {SponsorBlockSegmentsLoaded} payload - The event payload containing additional data about the segments loaded event.
797
912
  */
798
913
  sponsorBlockSegmentLoaded(player, track, payload) {
799
- return this.manager.emit(Manager_1.ManagerEventTypes.SegmentsLoaded, player, track, payload);
914
+ return this.manager.emit(Enums_1.ManagerEventTypes.SegmentsLoaded, player, track, payload);
800
915
  }
801
916
  /**
802
917
  * Emitted when a segment of a track is skipped using the sponsorblock plugin.
@@ -806,7 +921,7 @@ class Node {
806
921
  * @param {SponsorBlockSegmentSkipped} payload - The event payload containing additional data about the segment skipped event.
807
922
  */
808
923
  sponsorBlockSegmentSkipped(player, track, payload) {
809
- return this.manager.emit(Manager_1.ManagerEventTypes.SegmentSkipped, player, track, payload);
924
+ return this.manager.emit(Enums_1.ManagerEventTypes.SegmentSkipped, player, track, payload);
810
925
  }
811
926
  /**
812
927
  * Emitted when chapters for a track are loaded using the sponsorblock plugin.
@@ -816,7 +931,7 @@ class Node {
816
931
  * @param {SponsorBlockChaptersLoaded} payload - The event payload containing additional data about the chapters loaded event.
817
932
  */
818
933
  sponsorBlockChaptersLoaded(player, track, payload) {
819
- return this.manager.emit(Manager_1.ManagerEventTypes.ChaptersLoaded, player, track, payload);
934
+ return this.manager.emit(Enums_1.ManagerEventTypes.ChaptersLoaded, player, track, payload);
820
935
  }
821
936
  /**
822
937
  * Emitted when a chapter of a track is started using the sponsorblock plugin.
@@ -826,7 +941,37 @@ class Node {
826
941
  * @param {SponsorBlockChapterStarted} payload - The event payload containing additional data about the chapter started event.
827
942
  */
828
943
  sponsorBlockChapterStarted(player, track, payload) {
829
- return this.manager.emit(Manager_1.ManagerEventTypes.ChapterStarted, player, track, payload);
944
+ return this.manager.emit(Enums_1.ManagerEventTypes.ChapterStarted, player, track, payload);
945
+ }
946
+ /**
947
+ * Emitted when lyrics for a track are found.
948
+ * The payload of the event will contain the lyrics.
949
+ * @param {Player} player - The player associated with the lyrics.
950
+ * @param {Track} track - The track associated with the lyrics.
951
+ * @param {LyricsFoundEvent} payload - The event payload containing additional data about the lyrics found event.
952
+ */
953
+ lyricsFound(player, track, payload) {
954
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsFound, player, track, payload);
955
+ }
956
+ /**
957
+ * Emitted when lyrics for a track are not found.
958
+ * The payload of the event will contain the track.
959
+ * @param {Player} player - The player associated with the lyrics.
960
+ * @param {Track} track - The track associated with the lyrics.
961
+ * @param {LyricsNotFoundEvent} payload - The event payload containing additional data about the lyrics not found event.
962
+ */
963
+ lyricsNotFound(player, track, payload) {
964
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsNotFound, player, track, payload);
965
+ }
966
+ /**
967
+ * Emitted when a line of lyrics for a track is received.
968
+ * The payload of the event will contain the lyrics line.
969
+ * @param {Player} player - The player associated with the lyrics line.
970
+ * @param {Track} track - The track associated with the lyrics line.
971
+ * @param {LyricsLineEvent} payload - The event payload containing additional data about the lyrics line event.
972
+ */
973
+ lyricsLine(player, track, payload) {
974
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsLine, player, track, payload);
830
975
  }
831
976
  /**
832
977
  * Fetches Lavalink node information.
@@ -860,7 +1005,7 @@ class Node {
860
1005
  * player.setSponsorBlock([SponsorBlockSegment.Sponsor, SponsorBlockSegment.SelfPromo]);
861
1006
  * ```
862
1007
  */
863
- async setSponsorBlock(player, segments = [SponsorBlockSegment.Sponsor, SponsorBlockSegment.SelfPromo]) {
1008
+ async setSponsorBlock(player, segments = [Enums_1.SponsorBlockSegment.Sponsor, Enums_1.SponsorBlockSegment.SelfPromo]) {
864
1009
  if (!this.info.plugins.some((plugin) => plugin.name === "sponsorblock-plugin"))
865
1010
  throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.options.identifier}`);
866
1011
  if (!segments.length)
@@ -893,7 +1038,7 @@ class Node {
893
1038
  const message = "Please do NOT delete the magmastream/ folder as it is used to store player data for autoresume etc.";
894
1039
  if (!fs_1.default.existsSync(readmeFilePath)) {
895
1040
  fs_1.default.writeFileSync(readmeFilePath, message, "utf-8");
896
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Created README file at: ${readmeFilePath}`);
1041
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Created README file at: ${readmeFilePath}`);
897
1042
  }
898
1043
  }
899
1044
  }