magmastream 2.9.0-dev.9 → 2.9.1-dev.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.
@@ -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,48 @@ 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.Memory:
94
+ case Enums_1.StateStorageType.JSON:
95
+ this.sessionIdsFilePath = path_1.default.join(process.cwd(), "magmastream", "sessionData", "sessionIds.json");
96
+ const configDir = path_1.default.dirname(this.sessionIdsFilePath);
97
+ if (!fs_1.default.existsSync(configDir)) {
98
+ fs_1.default.mkdirSync(configDir, { recursive: true });
99
+ }
100
+ this.createSessionIdsFile();
101
+ this.createReadmeFile();
102
+ break;
103
+ case Enums_1.StateStorageType.Redis:
104
+ this.redisPrefix = this.manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
105
+ ? this.manager.options.stateStorage.redisConfig.prefix
106
+ : this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:";
107
+ break;
108
+ }
102
109
  }
103
- /** Returns if connected to the Node. */
110
+ /**
111
+ * Checks if the Node is currently connected.
112
+ * This method returns true if the Node has an active WebSocket connection, indicating it is ready to receive and process commands.
113
+ */
104
114
  get connected() {
105
115
  if (!this.socket)
106
116
  return false;
107
117
  return this.socket.readyState === ws_1.default.OPEN;
108
118
  }
109
- /** Returns the address for this node. */
119
+ /** Returns the full address for this node, including the host and port. */
110
120
  get address() {
111
121
  return `${this.options.host}:${this.options.port}`;
112
122
  }
113
- /** @hidden */
114
- static init(manager) {
115
- this._manager = manager;
116
- }
117
123
  /**
118
124
  * Creates the sessionIds.json file if it doesn't exist. This file is used to
119
125
  * store the session IDs for each node. The session IDs are used to identify
120
126
  * the node when resuming a session.
121
127
  */
122
128
  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");
129
+ if (!fs_1.default.existsSync(this.sessionIdsFilePath)) {
130
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Creating sessionId file at: ${this.sessionIdsFilePath}`);
131
+ fs_1.default.writeFileSync(this.sessionIdsFilePath, JSON.stringify({}), "utf-8");
128
132
  }
129
133
  }
130
134
  /**
@@ -135,21 +139,49 @@ class Node {
135
139
  * of the node identifier and cluster ID. This allows multiple clusters to
136
140
  * be used with the same node identifier.
137
141
  */
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);
142
+ async loadSessionIds() {
143
+ switch (this.manager.options.stateStorage.type) {
144
+ case Enums_1.StateStorageType.Memory:
145
+ case Enums_1.StateStorageType.JSON: {
146
+ if (fs_1.default.existsSync(this.sessionIdsFilePath)) {
147
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from file: ${this.sessionIdsFilePath}`);
148
+ const sessionIdsData = fs_1.default.readFileSync(this.sessionIdsFilePath, "utf-8");
149
+ this.sessionIdsMap = new Map(Object.entries(JSON.parse(sessionIdsData)));
150
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
151
+ if (this.sessionIdsMap.has(compositeKey)) {
152
+ this.sessionId = this.sessionIdsMap.get(compositeKey);
153
+ }
154
+ }
155
+ break;
152
156
  }
157
+ case Enums_1.StateStorageType.Redis:
158
+ const key = `${this.redisPrefix}node:sessionIds`;
159
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from Redis key: ${key}`);
160
+ const currentRaw = await this.manager.redis.get(key);
161
+ if (currentRaw) {
162
+ try {
163
+ const sessionIds = JSON.parse(currentRaw);
164
+ if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
165
+ throw new Error("[NODE] loadSessionIds invalid data type from Redis.");
166
+ }
167
+ this.sessionIdsMap = new Map(Object.entries(sessionIds));
168
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
169
+ if (this.sessionIdsMap.has(compositeKey)) {
170
+ this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
171
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
172
+ }
173
+ }
174
+ catch (err) {
175
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to parse Redis sessionIds: ${err.message}`);
176
+ this.sessionIdsMap = new Map();
177
+ }
178
+ }
179
+ else {
180
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] No sessionIds found in Redis — creating new key.`);
181
+ await this.manager.redis.set(key, JSON.stringify({}));
182
+ this.sessionIdsMap = new Map();
183
+ }
184
+ break;
153
185
  }
154
186
  }
155
187
  /**
@@ -163,15 +195,74 @@ class Node {
163
195
  * of the node identifier and cluster ID. This allows multiple clusters to
164
196
  * be used with the same node identifier.
165
197
  */
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)));
198
+ async updateSessionId() {
199
+ switch (this.manager.options.stateStorage.type) {
200
+ case Enums_1.StateStorageType.Memory:
201
+ case Enums_1.StateStorageType.JSON: {
202
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds to file: ${this.sessionIdsFilePath}`);
203
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
204
+ const filePath = this.sessionIdsFilePath;
205
+ let updated = false;
206
+ let retries = 3;
207
+ while (!updated && retries > 0) {
208
+ try {
209
+ let fileData = {};
210
+ if (fs_1.default.existsSync(filePath)) {
211
+ try {
212
+ const raw = fs_1.default.readFileSync(filePath, "utf-8");
213
+ fileData = raw.trim() ? JSON.parse(raw) : {};
214
+ }
215
+ catch (err) {
216
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to read/parse sessionIds.json: ${err.message}`);
217
+ fileData = {};
218
+ }
219
+ }
220
+ fileData[compositeKey] = this.sessionId;
221
+ const tmpPath = `${filePath}.tmp`;
222
+ fs_1.default.writeFileSync(tmpPath, JSON.stringify(fileData, null, 2), "utf-8");
223
+ fs_1.default.renameSync(tmpPath, filePath);
224
+ this.sessionIdsMap = new Map(Object.entries(fileData));
225
+ updated = true;
226
+ }
227
+ catch (err) {
228
+ retries--;
229
+ if (retries === 0) {
230
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to update sessionIds after retries: ${err.message}`);
231
+ throw err;
232
+ }
233
+ await new Promise((r) => setTimeout(r, 50));
234
+ }
235
+ }
236
+ break;
237
+ }
238
+ case Enums_1.StateStorageType.Redis: {
239
+ const key = `${this.redisPrefix}node:sessionIds`;
240
+ const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
241
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds in Redis key: ${key}`);
242
+ const currentRaw = await this.manager.redis.get(key);
243
+ let sessionIds;
244
+ if (currentRaw) {
245
+ try {
246
+ sessionIds = JSON.parse(currentRaw);
247
+ if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
248
+ throw new Error("Invalid data type in Redis");
249
+ }
250
+ }
251
+ catch (err) {
252
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Corrupted Redis sessionIds, reinitializing: ${err.message}`);
253
+ sessionIds = {};
254
+ }
255
+ }
256
+ else {
257
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Redis key not found — creating new sessionIds key.`);
258
+ sessionIds = {};
259
+ }
260
+ sessionIds[compositeKey] = this.sessionId;
261
+ this.sessionIdsMap = new Map(Object.entries(sessionIds));
262
+ await this.manager.redis.set(key, JSON.stringify(sessionIds));
263
+ break;
264
+ }
265
+ }
175
266
  }
176
267
  /**
177
268
  * Connects to the Node.
@@ -182,7 +273,8 @@ class Node {
182
273
  * If the node has no session ID but the `enableSessionResumeOption` option is true, it will use the session ID
183
274
  * stored in the sessionIds.json file if it exists.
184
275
  */
185
- connect() {
276
+ async connect() {
277
+ await this.loadSessionIds();
186
278
  if (this.connected)
187
279
  return;
188
280
  const headers = {
@@ -194,13 +286,14 @@ class Node {
194
286
  if (this.sessionId) {
195
287
  headers["Session-Id"] = this.sessionId;
196
288
  }
197
- else if (this.options.enableSessionResumeOption && sessionIdsMap.has(compositeKey)) {
198
- this.sessionId = sessionIdsMap.get(compositeKey) || null;
289
+ else if (this.options.enableSessionResumeOption && this.sessionIdsMap.has(compositeKey)) {
290
+ this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
199
291
  headers["Session-Id"] = this.sessionId;
200
292
  }
201
293
  this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
202
294
  this.socket.on("open", this.open.bind(this));
203
295
  this.socket.on("close", this.close.bind(this));
296
+ this.socket.on("upgrade", (request) => this.upgrade(request));
204
297
  this.socket.on("message", this.message.bind(this));
205
298
  this.socket.on("error", this.error.bind(this));
206
299
  const debugInfo = {
@@ -214,7 +307,7 @@ class Node {
214
307
  identifier: this.options.identifier,
215
308
  },
216
309
  };
217
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Connecting ${JSON.stringify(debugInfo)}`);
310
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Connecting ${JSON.stringify(debugInfo)}`);
218
311
  }
219
312
  /**
220
313
  * Destroys the node and cleans up associated resources.
@@ -229,33 +322,27 @@ class Node {
229
322
  async destroy() {
230
323
  if (!this.connected)
231
324
  return;
232
- // Emit a debug event indicating that the node is being destroyed
233
325
  const debugInfo = {
234
326
  connected: this.connected,
235
327
  identifier: this.options.identifier,
236
328
  address: this.address,
237
329
  sessionId: this.sessionId,
238
- playerCount: (await this.manager.players.filter((p) => p.node == this)).size,
330
+ playerCount: this.manager.players.filter((p) => p.node == this).size,
239
331
  };
240
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Destroying node: ${JSON.stringify(debugInfo)}`);
332
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Destroying node: ${JSON.stringify(debugInfo)}`);
241
333
  // Automove all players connected to that node
242
- const players = await this.manager.players.filter((p) => p.node == this);
334
+ const players = this.manager.players.filter((p) => p.node == this);
243
335
  if (players.size) {
244
- players.forEach(async (player) => {
336
+ for (const player of players.values()) {
245
337
  await player.autoMoveNode();
246
- });
338
+ }
247
339
  }
248
- // Close the WebSocket connection
249
340
  this.socket.close(1000, "destroy");
250
- // Remove all event listeners on the WebSocket
251
341
  this.socket.removeAllListeners();
252
- // Clear the reconnect timeout
253
342
  this.reconnectAttempts = 1;
254
343
  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);
344
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeDestroy, this);
345
+ this.manager.nodes.delete(this.options.identifier);
259
346
  }
260
347
  /**
261
348
  * Attempts to reconnect to the node if the connection is lost.
@@ -274,7 +361,6 @@ class Node {
274
361
  * @emits {nodeDestroy} - Emits a nodeDestroy event if the maximum number of retry attempts is reached.
275
362
  */
276
363
  async reconnect() {
277
- // Collect debug information regarding the current state of the node
278
364
  const debugInfo = {
279
365
  identifier: this.options.identifier,
280
366
  connected: this.connected,
@@ -282,27 +368,28 @@ class Node {
282
368
  maxRetryAttempts: this.options.maxRetryAttempts,
283
369
  retryDelayMs: this.options.retryDelayMs,
284
370
  };
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
371
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Reconnecting node: ${JSON.stringify(debugInfo)}`);
288
372
  this.reconnectTimeout = setTimeout(async () => {
289
- // Check if the maximum number of retry attempts has been reached
290
373
  if (this.reconnectAttempts >= this.options.maxRetryAttempts) {
291
- // Emit an error event and destroy the node if retries are exhausted
292
374
  const error = new Error(`Unable to connect after ${this.options.maxRetryAttempts} attempts.`);
293
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, error);
375
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
294
376
  return await this.destroy();
295
377
  }
296
- // Remove all listeners from the current WebSocket and reset it
297
378
  this.socket?.removeAllListeners();
298
379
  this.socket = null;
299
- // Emit a nodeReconnect event and attempt to connect again
300
- this.manager.emit(Manager_1.ManagerEventTypes.NodeReconnect, this);
380
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeReconnect, this);
301
381
  this.connect();
302
- // Increment the reconnect attempts counter
303
382
  this.reconnectAttempts++;
304
383
  }, this.options.retryDelayMs);
305
384
  }
385
+ /**
386
+ * Upgrades the node to a NodeLink.
387
+ *
388
+ * @param request - The incoming message.
389
+ */
390
+ upgrade(request) {
391
+ this.isNodeLink = this.options.isNodeLink ?? Boolean(request.headers.isnodelink) ?? false;
392
+ }
306
393
  /**
307
394
  * Handles the "open" event emitted by the WebSocket connection.
308
395
  *
@@ -312,18 +399,18 @@ class Node {
312
399
  * with the node as the argument.
313
400
  */
314
401
  open() {
315
- // Clear any existing reconnect timeouts
316
402
  if (this.reconnectTimeout)
317
403
  clearTimeout(this.reconnectTimeout);
318
- // Collect debug information regarding the current state of the node
319
404
  const debugInfo = {
320
405
  identifier: this.options.identifier,
321
406
  connected: this.connected,
322
407
  };
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);
408
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Connected node: ${JSON.stringify(debugInfo)}`);
409
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeConnect, this);
410
+ const playersOnBackupNode = this.manager.players.filter((p) => p.node.options.isBackup);
411
+ if (playersOnBackupNode.size) {
412
+ Promise.all(Array.from(playersOnBackupNode.values(), (player) => player.moveNode(this.options.identifier)));
413
+ }
327
414
  }
328
415
  /**
329
416
  * Handles the "close" event emitted by the WebSocket connection.
@@ -344,18 +431,21 @@ class Node {
344
431
  code,
345
432
  reason,
346
433
  };
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
434
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeDisconnect, this, { code, reason });
435
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Disconnected node: ${JSON.stringify(debugInfo)}`);
352
436
  if (this.manager.useableNode) {
353
- const players = await this.manager.players.filter((p) => p.node.options.identifier == this.options.identifier);
437
+ const players = this.manager.players.filter((p) => p.node.options.identifier == this.options.identifier);
354
438
  if (players.size) {
355
439
  await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
356
440
  }
357
441
  }
358
- // If the close event was not initiated by the user, attempt to reconnect
442
+ else {
443
+ const backUpNodes = this.manager.nodes.filter((node) => node.options.isBackup && node.connected);
444
+ const backupNode = backUpNodes.first();
445
+ if (backupNode) {
446
+ await Promise.all(Array.from(this.manager.players.values(), (player) => player.moveNode(backupNode.options.identifier)));
447
+ }
448
+ }
359
449
  if (code !== 1000 || reason !== "destroy")
360
450
  await this.reconnect();
361
451
  }
@@ -370,15 +460,12 @@ class Node {
370
460
  error(error) {
371
461
  if (!error)
372
462
  return;
373
- // Collect debug information regarding the error
374
463
  const debugInfo = {
375
464
  identifier: this.options.identifier,
376
465
  error: error.message,
377
466
  };
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);
467
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Error on node: ${JSON.stringify(debugInfo)}`);
468
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
382
469
  }
383
470
  /**
384
471
  * Handles incoming messages from the Lavalink WebSocket connection.
@@ -397,7 +484,7 @@ class Node {
397
484
  const payload = JSON.parse(d.toString());
398
485
  if (!payload.op)
399
486
  return;
400
- this.manager.emit(Manager_1.ManagerEventTypes.NodeRaw, payload);
487
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeRaw, payload);
401
488
  let player;
402
489
  switch (payload.op) {
403
490
  case "stats":
@@ -405,7 +492,7 @@ class Node {
405
492
  this.stats = { ...payload };
406
493
  break;
407
494
  case "playerUpdate":
408
- player = await this.manager.players.get(payload.guildId);
495
+ player = this.manager.players.get(payload.guildId);
409
496
  if (player && player.node.options.identifier !== this.options.identifier) {
410
497
  return;
411
498
  }
@@ -413,33 +500,31 @@ class Node {
413
500
  player.position = payload.state.position || 0;
414
501
  break;
415
502
  case "event":
416
- player = await this.manager.players.get(payload.guildId);
503
+ player = this.manager.players.get(payload.guildId);
417
504
  if (player && player.node.options.identifier !== this.options.identifier) {
418
505
  return;
419
506
  }
420
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
507
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
421
508
  await this.handleEvent(payload);
422
509
  break;
423
510
  case "ready":
424
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
511
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Node message: ${JSON.stringify(payload)}`);
425
512
  this.rest.setSessionId(payload.sessionId);
426
513
  this.sessionId = payload.sessionId;
427
- this.updateSessionId(); // Call to update session ID
514
+ await this.updateSessionId();
428
515
  this.info = await this.fetchInfo();
429
- // Log if the session was resumed successfully
430
516
  if (payload.resumed) {
431
- // Load player states from the JSON file
432
517
  await this.manager.loadPlayerStates(this.options.identifier);
433
518
  }
434
519
  if (this.options.enableSessionResumeOption) {
435
520
  await this.rest.patch(`/v4/sessions/${this.sessionId}`, {
436
521
  resuming: this.options.enableSessionResumeOption,
437
- timeout: this.options.sessionTimeoutMs,
522
+ timeout: this.options.sessionTimeoutSeconds,
438
523
  });
439
524
  }
440
525
  break;
441
526
  default:
442
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected op "${payload.op}" with data: ${payload.message}`));
527
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected op "${payload.op}" with data: ${payload.message}`));
443
528
  return;
444
529
  }
445
530
  }
@@ -452,7 +537,7 @@ class Node {
452
537
  async handleEvent(payload) {
453
538
  if (!payload.guildId)
454
539
  return;
455
- const player = await this.manager.players.get(payload.guildId);
540
+ const player = this.manager.players.get(payload.guildId);
456
541
  if (!player)
457
542
  return;
458
543
  const track = await player.queue.getCurrent();
@@ -489,9 +574,18 @@ class Node {
489
574
  case "ChapterStarted":
490
575
  this.sponsorBlockChapterStarted(player, track, payload);
491
576
  break;
577
+ case "LyricsFoundEvent":
578
+ this.lyricsFound(player, track, payload);
579
+ break;
580
+ case "LyricsNotFoundEvent":
581
+ this.lyricsNotFound(player, track, payload);
582
+ break;
583
+ case "LyricsLineEvent":
584
+ this.lyricsLine(player, track, payload);
585
+ break;
492
586
  default:
493
587
  error = new Error(`Node#event unknown event '${type}'.`);
494
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, error);
588
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, error);
495
589
  break;
496
590
  }
497
591
  }
@@ -506,22 +600,24 @@ class Node {
506
600
  const oldPlayer = player;
507
601
  player.playing = true;
508
602
  player.paused = false;
509
- this.manager.emit(Manager_1.ManagerEventTypes.TrackStart, player, track, payload);
603
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackStart, player, track, payload);
510
604
  const botUser = player.get("Internal_BotUser");
511
605
  if (botUser && botUser.id === track.requester.id) {
512
- this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
513
- changeType: Manager_1.PlayerStateEventTypes.TrackChange,
606
+ this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
607
+ changeType: Enums_1.PlayerStateEventTypes.TrackChange,
514
608
  details: {
515
- changeType: "autoPlay",
609
+ type: "track",
610
+ action: "autoPlay",
516
611
  track: track,
517
612
  },
518
613
  });
519
614
  return;
520
615
  }
521
- this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
522
- changeType: Manager_1.PlayerStateEventTypes.TrackChange,
616
+ this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
617
+ changeType: Enums_1.PlayerStateEventTypes.TrackChange,
523
618
  details: {
524
- changeType: "start",
619
+ type: "track",
620
+ action: "start",
525
621
  track: track,
526
622
  },
527
623
  });
@@ -539,26 +635,23 @@ class Node {
539
635
  const previous = await player.queue.getPrevious();
540
636
  const current = await player.queue.getCurrent();
541
637
  if (!skipFlag && (previous.length === 0 || (previous[0] && previous[0].track !== current?.track))) {
542
- // Store the current track in the previous tracks queue
543
- await player.queue.addPrevious(await player.queue.getCurrent());
544
- // Limit the previous tracks queue to maxPreviousTracks
545
- if ((await player.queue.getPrevious()).length > this.manager.options.maxPreviousTracks) {
546
- (await player.queue.getPrevious()).shift();
638
+ await player.queue.addPrevious(current);
639
+ const updated = await player.queue.getPrevious();
640
+ if (updated.length > this.manager.options.maxPreviousTracks) {
641
+ const trimmed = updated.slice(0, this.manager.options.maxPreviousTracks);
642
+ await player.queue.setPrevious(trimmed);
547
643
  }
548
644
  }
645
+ player.set("skipFlag", false);
549
646
  const oldPlayer = player;
550
- // Handle track end events
551
647
  switch (reason) {
552
- case Utils_1.TrackEndReasonTypes.LoadFailed:
553
- case Utils_1.TrackEndReasonTypes.Cleanup:
554
- // Handle the case when a track failed to load or was cleaned up
648
+ case Enums_1.TrackEndReasonTypes.LoadFailed:
649
+ case Enums_1.TrackEndReasonTypes.Cleanup:
555
650
  await this.handleFailedTrack(player, track, payload);
556
651
  break;
557
- case Utils_1.TrackEndReasonTypes.Replaced:
558
- // Handle the case when a track was replaced
652
+ case Enums_1.TrackEndReasonTypes.Replaced:
559
653
  break;
560
- case Utils_1.TrackEndReasonTypes.Stopped:
561
- // If the track was forcibly replaced
654
+ case Enums_1.TrackEndReasonTypes.Stopped:
562
655
  if (await player.queue.size()) {
563
656
  await this.playNextTrack(player, track, payload);
564
657
  }
@@ -566,7 +659,7 @@ class Node {
566
659
  await this.queueEnd(player, track, payload);
567
660
  }
568
661
  break;
569
- case Utils_1.TrackEndReasonTypes.Finished:
662
+ case Enums_1.TrackEndReasonTypes.Finished:
570
663
  // If the track ended and it's set to repeat (track or queue)
571
664
  if (track && (player.trackRepeat || player.queueRepeat)) {
572
665
  await this.handleRepeatedTrack(player, track, payload);
@@ -581,13 +674,14 @@ class Node {
581
674
  }
582
675
  break;
583
676
  default:
584
- this.manager.emit(Manager_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected track end reason "${reason}"`));
677
+ this.manager.emit(Enums_1.ManagerEventTypes.NodeError, this, new Error(`Unexpected track end reason "${reason}"`));
585
678
  break;
586
679
  }
587
- this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
588
- changeType: Manager_1.PlayerStateEventTypes.TrackChange,
680
+ this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
681
+ changeType: Enums_1.PlayerStateEventTypes.TrackChange,
589
682
  details: {
590
- changeType: "end",
683
+ type: "track",
684
+ action: "end",
591
685
  track: track,
592
686
  },
593
687
  });
@@ -606,13 +700,20 @@ class Node {
606
700
  // If autoplay is not enabled or all attempts have failed, early exit
607
701
  if (!player.isAutoplay || attempt > player.autoplayTries || !(await player.queue.getPrevious()).length)
608
702
  return false;
609
- const lastTrack = await player.queue.getPrevious()[(await player.queue.getPrevious()).length - 1];
703
+ const PreviousQueue = await player.queue.getPrevious();
704
+ const lastTrack = PreviousQueue?.at(-1);
610
705
  lastTrack.requester = player.get("Internal_BotUser");
611
706
  if (!lastTrack)
612
707
  return false;
613
708
  const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(lastTrack);
614
- if (tracks.length) {
615
- await player.queue.add(tracks[0]);
709
+ const normalize = (str) => str
710
+ .toLowerCase()
711
+ .replace(/\s+/g, "")
712
+ .replace(/[^a-z0-9]/g, "");
713
+ const filteredTracks = tracks.filter((track) => track.identifier !== lastTrack.identifier && track.uri !== lastTrack.uri && normalize(track.title) !== normalize(lastTrack.title));
714
+ if (filteredTracks.length) {
715
+ const randomTrack = filteredTracks[Math.floor(Math.random() * filteredTracks.length)];
716
+ await player.queue.add(randomTrack);
616
717
  await player.play();
617
718
  return true;
618
719
  }
@@ -638,7 +739,7 @@ class Node {
638
739
  await this.queueEnd(player, track, payload);
639
740
  return;
640
741
  }
641
- this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
742
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
642
743
  if (this.manager.options.playNextOnEnd)
643
744
  await player.play();
644
745
  }
@@ -671,10 +772,8 @@ class Node {
671
772
  }
672
773
  // Move to the next track
673
774
  await queue.setCurrent(await queue.dequeue());
674
- // Emit track end event
675
- this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
676
- // If the track was stopped manually and no more tracks exist, end the queue
677
- if (payload.reason === Utils_1.TrackEndReasonTypes.Stopped) {
775
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
776
+ if (payload.reason === Enums_1.TrackEndReasonTypes.Stopped) {
678
777
  const next = await queue.dequeue();
679
778
  await queue.setCurrent(next ?? null);
680
779
  if (!next) {
@@ -682,7 +781,6 @@ class Node {
682
781
  return;
683
782
  }
684
783
  }
685
- // If autoplay is enabled, play the next track
686
784
  if (playNextOnEnd)
687
785
  await player.play();
688
786
  }
@@ -700,9 +798,7 @@ class Node {
700
798
  async playNextTrack(player, track, payload) {
701
799
  // Shift the queue to set the next track as current
702
800
  await player.queue.setCurrent(await player.queue.dequeue());
703
- // Emit the track end event
704
- this.manager.emit(Manager_1.ManagerEventTypes.TrackEnd, player, track, payload);
705
- // If autoplay is enabled, play the next track
801
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackEnd, player, track, payload);
706
802
  if (this.manager.options.playNextOnEnd)
707
803
  await player.play();
708
804
  }
@@ -719,7 +815,7 @@ class Node {
719
815
  await player.queue.setCurrent(null);
720
816
  if (!player.isAutoplay) {
721
817
  player.playing = false;
722
- this.manager.emit(Manager_1.ManagerEventTypes.QueueEnd, player, track, payload);
818
+ this.manager.emit(Enums_1.ManagerEventTypes.QueueEnd, player, track, payload);
723
819
  return;
724
820
  }
725
821
  let attempt = 1;
@@ -730,24 +826,34 @@ class Node {
730
826
  return;
731
827
  attempt++;
732
828
  }
733
- // If all attempts fail, reset the player state and emit queueEnd
734
829
  player.playing = false;
735
- this.manager.emit(Manager_1.ManagerEventTypes.QueueEnd, player, track, payload);
830
+ this.manager.emit(Enums_1.ManagerEventTypes.QueueEnd, player, track, payload);
736
831
  }
737
832
  /**
738
833
  * Fetches the lyrics of a track from the Lavalink node.
739
- * This method uses the `lavalyrics-plugin` to fetch the lyrics.
740
- * If the plugin is not available, it will throw a RangeError.
834
+ *
835
+ * If the node is a NodeLink, it will use the `NodeLinkGetLyrics` method to fetch the lyrics.
836
+ *
837
+ * Requires the `lavalyrics-plugin` to be present in the Lavalink node.
838
+ * Requires the `lavasrc-plugin` or `java-lyrics-plugin` to be present in the Lavalink node.
741
839
  *
742
840
  * @param {Track} track - The track to fetch the lyrics for.
743
841
  * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
744
- * @returns {Promise<Lyrics>} A promise that resolves with the lyrics data.
842
+ * @param {string} [language="en"] - The language of the lyrics.
843
+ * @returns {Promise<Lyrics | NodeLinkGetLyrics>} A promise that resolves with the lyrics data.
745
844
  */
746
- async getLyrics(track, skipTrackSource = false) {
747
- if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin"))
748
- throw new RangeError(`there is no lavalyrics-plugin available in the lavalink node: ${this.options.identifier}`);
749
- // Make a GET request to the Lavalink node to fetch the lyrics
750
- // The request includes the track URL and the skipTrackSource parameter
845
+ async getLyrics(track, skipTrackSource = false, language) {
846
+ if (!this.connected)
847
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
848
+ if (this.isNodeLink) {
849
+ return (await this.rest.get(`/v4/loadlyrics?encodedTrack=${encodeURIComponent(track.track)}${language ? `&language=${language}` : ""}`));
850
+ }
851
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin")) {
852
+ throw new RangeError(`The plugin "lavalyrics-plugin" must be present in the lavalink node: ${this.options.identifier}`);
853
+ }
854
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavasrc-plugin" || plugin.name === "java-lyrics-plugin")) {
855
+ 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})`);
856
+ }
751
857
  return ((await this.rest.get(`/v4/lyrics?track=${encodeURIComponent(track.track)}&skipTrackSource=${skipTrackSource}`)) || {
752
858
  source: null,
753
859
  provider: null,
@@ -756,6 +862,42 @@ class Node {
756
862
  plugin: [],
757
863
  });
758
864
  }
865
+ /**
866
+ * Subscribes to lyrics for a player.
867
+ * @param {string} guildId - The ID of the guild to subscribe to lyrics for.
868
+ * @param {boolean} [skipTrackSource=false] - Whether to skip using the track's source URL.
869
+ * @returns {Promise<unknown>} A promise that resolves when the subscription is complete.
870
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
871
+ */
872
+ async lyricsSubscribe(guildId, skipTrackSource = false) {
873
+ if (!this.connected)
874
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
875
+ if (this.isNodeLink)
876
+ throw new RangeError(`The node is a node link: ${this.options.identifier}`);
877
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavalyrics-plugin")) {
878
+ throw new RangeError(`The plugin "lavalyrics-plugin" must be present in the lavalink node: ${this.options.identifier}`);
879
+ }
880
+ if (!this.info.plugins.some((plugin) => plugin.name === "lavasrc-plugin" || plugin.name === "java-lyrics-plugin")) {
881
+ 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})`);
882
+ }
883
+ return await this.rest.post(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource}`, {});
884
+ }
885
+ /**
886
+ * Unsubscribes from lyrics for a player.
887
+ * @param {string} guildId - The ID of the guild to unsubscribe from lyrics for.
888
+ * @returns {Promise<unknown>} A promise that resolves when the unsubscription is complete.
889
+ * @throws {RangeError} If the node is not connected to the lavalink server or if the java-lyrics-plugin is not available.
890
+ */
891
+ async lyricsUnsubscribe(guildId) {
892
+ if (!this.connected)
893
+ throw new RangeError(`The node is not connected to the lavalink server: ${this.options.identifier}`);
894
+ if (this.isNodeLink)
895
+ throw new RangeError(`The node is a node link: ${this.options.identifier}`);
896
+ if (!this.info.plugins.some((plugin) => plugin.name === "java-lyrics-plugin")) {
897
+ throw new RangeError(`there is no java-lyrics-plugin available in the lavalink node: ${this.options.identifier}`);
898
+ }
899
+ return await this.rest.delete(`/v4/sessions/${this.sessionId}/players/${guildId}/lyrics/subscribe`);
900
+ }
759
901
  /**
760
902
  * Handles the event when a track becomes stuck during playback.
761
903
  * Stops the current track and emits a `trackStuck` event.
@@ -768,7 +910,7 @@ class Node {
768
910
  */
769
911
  async trackStuck(player, track, payload) {
770
912
  await player.stop();
771
- this.manager.emit(Manager_1.ManagerEventTypes.TrackStuck, player, track, payload);
913
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackStuck, player, track, payload);
772
914
  }
773
915
  /**
774
916
  * Handles the event when a track has an error during playback.
@@ -782,7 +924,7 @@ class Node {
782
924
  */
783
925
  async trackError(player, track, payload) {
784
926
  await player.stop();
785
- this.manager.emit(Manager_1.ManagerEventTypes.TrackError, player, track, payload);
927
+ this.manager.emit(Enums_1.ManagerEventTypes.TrackError, player, track, payload);
786
928
  }
787
929
  /**
788
930
  * Emitted when the WebSocket connection for a player closes.
@@ -791,8 +933,8 @@ class Node {
791
933
  * @param {WebSocketClosedEvent} payload - The event payload containing additional data about the WebSocket close event.
792
934
  */
793
935
  socketClosed(player, payload) {
794
- this.manager.emit(Manager_1.ManagerEventTypes.SocketClosed, player, payload);
795
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Websocket closed for player: ${player.guildId} with payload: ${JSON.stringify(payload)}`);
936
+ this.manager.emit(Enums_1.ManagerEventTypes.SocketClosed, player, payload);
937
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Websocket closed for player: ${player.guildId} with payload: ${JSON.stringify(payload)}`);
796
938
  }
797
939
  /**
798
940
  * Emitted when the segments for a track are loaded.
@@ -802,7 +944,7 @@ class Node {
802
944
  * @param {SponsorBlockSegmentsLoaded} payload - The event payload containing additional data about the segments loaded event.
803
945
  */
804
946
  sponsorBlockSegmentLoaded(player, track, payload) {
805
- return this.manager.emit(Manager_1.ManagerEventTypes.SegmentsLoaded, player, track, payload);
947
+ return this.manager.emit(Enums_1.ManagerEventTypes.SegmentsLoaded, player, track, payload);
806
948
  }
807
949
  /**
808
950
  * Emitted when a segment of a track is skipped using the sponsorblock plugin.
@@ -812,7 +954,7 @@ class Node {
812
954
  * @param {SponsorBlockSegmentSkipped} payload - The event payload containing additional data about the segment skipped event.
813
955
  */
814
956
  sponsorBlockSegmentSkipped(player, track, payload) {
815
- return this.manager.emit(Manager_1.ManagerEventTypes.SegmentSkipped, player, track, payload);
957
+ return this.manager.emit(Enums_1.ManagerEventTypes.SegmentSkipped, player, track, payload);
816
958
  }
817
959
  /**
818
960
  * Emitted when chapters for a track are loaded using the sponsorblock plugin.
@@ -822,7 +964,7 @@ class Node {
822
964
  * @param {SponsorBlockChaptersLoaded} payload - The event payload containing additional data about the chapters loaded event.
823
965
  */
824
966
  sponsorBlockChaptersLoaded(player, track, payload) {
825
- return this.manager.emit(Manager_1.ManagerEventTypes.ChaptersLoaded, player, track, payload);
967
+ return this.manager.emit(Enums_1.ManagerEventTypes.ChaptersLoaded, player, track, payload);
826
968
  }
827
969
  /**
828
970
  * Emitted when a chapter of a track is started using the sponsorblock plugin.
@@ -832,7 +974,37 @@ class Node {
832
974
  * @param {SponsorBlockChapterStarted} payload - The event payload containing additional data about the chapter started event.
833
975
  */
834
976
  sponsorBlockChapterStarted(player, track, payload) {
835
- return this.manager.emit(Manager_1.ManagerEventTypes.ChapterStarted, player, track, payload);
977
+ return this.manager.emit(Enums_1.ManagerEventTypes.ChapterStarted, player, track, payload);
978
+ }
979
+ /**
980
+ * Emitted when lyrics for a track are found.
981
+ * The payload of the event will contain the lyrics.
982
+ * @param {Player} player - The player associated with the lyrics.
983
+ * @param {Track} track - The track associated with the lyrics.
984
+ * @param {LyricsFoundEvent} payload - The event payload containing additional data about the lyrics found event.
985
+ */
986
+ lyricsFound(player, track, payload) {
987
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsFound, player, track, payload);
988
+ }
989
+ /**
990
+ * Emitted when lyrics for a track are not found.
991
+ * The payload of the event will contain the track.
992
+ * @param {Player} player - The player associated with the lyrics.
993
+ * @param {Track} track - The track associated with the lyrics.
994
+ * @param {LyricsNotFoundEvent} payload - The event payload containing additional data about the lyrics not found event.
995
+ */
996
+ lyricsNotFound(player, track, payload) {
997
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsNotFound, player, track, payload);
998
+ }
999
+ /**
1000
+ * Emitted when a line of lyrics for a track is received.
1001
+ * The payload of the event will contain the lyrics line.
1002
+ * @param {Player} player - The player associated with the lyrics line.
1003
+ * @param {Track} track - The track associated with the lyrics line.
1004
+ * @param {LyricsLineEvent} payload - The event payload containing additional data about the lyrics line event.
1005
+ */
1006
+ lyricsLine(player, track, payload) {
1007
+ return this.manager.emit(Enums_1.ManagerEventTypes.LyricsLine, player, track, payload);
836
1008
  }
837
1009
  /**
838
1010
  * Fetches Lavalink node information.
@@ -866,7 +1038,7 @@ class Node {
866
1038
  * player.setSponsorBlock([SponsorBlockSegment.Sponsor, SponsorBlockSegment.SelfPromo]);
867
1039
  * ```
868
1040
  */
869
- async setSponsorBlock(player, segments = [SponsorBlockSegment.Sponsor, SponsorBlockSegment.SelfPromo]) {
1041
+ async setSponsorBlock(player, segments = [Enums_1.SponsorBlockSegment.Sponsor, Enums_1.SponsorBlockSegment.SelfPromo]) {
870
1042
  if (!this.info.plugins.some((plugin) => plugin.name === "sponsorblock-plugin"))
871
1043
  throw new RangeError(`there is no sponsorblock-plugin available in the lavalink node: ${this.options.identifier}`);
872
1044
  if (!segments.length)
@@ -899,7 +1071,7 @@ class Node {
899
1071
  const message = "Please do NOT delete the magmastream/ folder as it is used to store player data for autoresume etc.";
900
1072
  if (!fs_1.default.existsSync(readmeFilePath)) {
901
1073
  fs_1.default.writeFileSync(readmeFilePath, message, "utf-8");
902
- this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[NODE] Created README file at: ${readmeFilePath}`);
1074
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Created README file at: ${readmeFilePath}`);
903
1075
  }
904
1076
  }
905
1077
  }