lavalink-client 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/cjs/structures/Filters.js +3 -2
  2. package/dist/cjs/structures/LavalinkManager.d.ts +1 -1
  3. package/dist/cjs/structures/LavalinkManager.js +15 -15
  4. package/dist/cjs/structures/LavalinkManagerStatics.js +15 -1
  5. package/dist/cjs/structures/Node.d.ts +1 -1
  6. package/dist/cjs/structures/Node.js +20 -10
  7. package/dist/cjs/structures/Player.js +56 -13
  8. package/dist/cjs/structures/Types/Manager.d.ts +2 -0
  9. package/dist/cjs/structures/Types/Track.d.ts +1 -1
  10. package/dist/cjs/structures/Types/Utils.d.ts +5 -5
  11. package/dist/cjs/structures/Utils.d.ts +2 -1
  12. package/dist/cjs/structures/Utils.js +46 -4
  13. package/dist/esm/structures/Filters.js +3 -2
  14. package/dist/esm/structures/LavalinkManager.d.ts +1 -1
  15. package/dist/esm/structures/LavalinkManager.js +16 -16
  16. package/dist/esm/structures/LavalinkManagerStatics.js +15 -1
  17. package/dist/esm/structures/Node.d.ts +1 -1
  18. package/dist/esm/structures/Node.js +21 -11
  19. package/dist/esm/structures/Player.js +57 -14
  20. package/dist/esm/structures/Types/Manager.d.ts +2 -0
  21. package/dist/esm/structures/Types/Track.d.ts +1 -1
  22. package/dist/esm/structures/Types/Utils.d.ts +5 -5
  23. package/dist/esm/structures/Utils.d.ts +2 -1
  24. package/dist/esm/structures/Utils.js +45 -4
  25. package/dist/types/structures/LavalinkManager.d.ts +1 -1
  26. package/dist/types/structures/Node.d.ts +1 -1
  27. package/dist/types/structures/Types/Manager.d.ts +2 -0
  28. package/dist/types/structures/Types/Track.d.ts +1 -1
  29. package/dist/types/structures/Types/Utils.d.ts +5 -5
  30. package/dist/types/structures/Utils.d.ts +2 -1
  31. package/package.json +11 -11
@@ -1,4 +1,5 @@
1
1
  import { audioOutputsData } from "./Constants.js";
2
+ import { safeStringify } from "./Utils.js";
2
3
  /**
3
4
  * The FilterManager for each player
4
5
  */
@@ -670,7 +671,7 @@ export class FilterManager {
670
671
  async setEQ(bands) {
671
672
  if (!Array.isArray(bands))
672
673
  bands = [bands];
673
- if (!bands.length || !bands.every((band) => JSON.stringify(Object.keys(band).sort()) === '["band","gain"]'))
674
+ if (!bands.length || !bands.every((band) => safeStringify(Object.keys(band).sort()) === '["band","gain"]'))
674
675
  throw new TypeError("Bands must be a non-empty object array containing 'band' and 'gain' properties.");
675
676
  for (const { band, gain } of bands)
676
677
  this.equalizerBands[band] = { band, gain };
@@ -690,6 +691,6 @@ export class FilterManager {
690
691
  }
691
692
  /** Clears the equalizer bands. */
692
693
  async clearEQ() {
693
- return this.setEQ(new Array(15).fill(0.0).map((gain, band) => ({ band, gain })));
694
+ return this.setEQ(Array.from({ length: 15 }, () => ({ band: 0, gain: 0 })));
694
695
  }
695
696
  }
@@ -2,9 +2,9 @@ import { EventEmitter } from "events";
2
2
  import { NodeManager } from "./NodeManager.js";
3
3
  import { Player } from "./Player.js";
4
4
  import { ManagerUtils, MiniMap } from "./Utils.js";
5
+ import type { ChannelDeletePacket, VoicePacket, VoiceServer, VoiceState } from "./Types/Utils.js";
5
6
  import type { BotClientOptions, LavalinkManagerEvents, ManagerOptions } from "./Types/Manager.js";
6
7
  import type { PlayerOptions } from "./Types/Player.js";
7
- import type { ChannelDeletePacket, VoicePacket, VoiceServer, VoiceState } from "./Types/Utils.js";
8
8
  export declare class LavalinkManager extends EventEmitter {
9
9
  /**
10
10
  * Emit an event
@@ -3,7 +3,7 @@ import { DebugEvents, DestroyReasons } from "./Constants.js";
3
3
  import { NodeManager } from "./NodeManager.js";
4
4
  import { Player } from "./Player.js";
5
5
  import { DefaultQueueStore } from "./Queue.js";
6
- import { ManagerUtils, MiniMap } from "./Utils.js";
6
+ import { ManagerUtils, MiniMap, safeStringify } from "./Utils.js";
7
7
  export class LavalinkManager extends EventEmitter {
8
8
  /**
9
9
  * Emit an event
@@ -68,7 +68,7 @@ export class LavalinkManager extends EventEmitter {
68
68
  applyOptions(options) {
69
69
  this.options = {
70
70
  client: {
71
- ...(options?.client || {}),
71
+ ...options?.client,
72
72
  id: options?.client?.id,
73
73
  username: options?.client?.username ?? "lavalink-client"
74
74
  },
@@ -311,7 +311,7 @@ export class LavalinkManager extends EventEmitter {
311
311
  // oldPlayer.connected is operational. you could also do oldPlayer.voice?.token
312
312
  if (oldPlayer.voiceChannelId === "string" && oldPlayer.connected && !oldPlayer.get("internal_destroywithoutdisconnect")) {
313
313
  if (!this.options?.advancedOptions?.debugOptions?.playerDestroy?.dontThrowError)
314
- throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${JSON.stringify(oldPlayer.toJSON?.())}`);
314
+ throw new Error(`Use Player#destroy() not LavalinkManager#deletePlayer() to stop the Player ${safeStringify(oldPlayer.toJSON?.())}`);
315
315
  else if (this.options?.advancedOptions?.enableDebugEvents) {
316
316
  this.emit("debug", DebugEvents.PlayerDeleteInsteadOfDestroy, {
317
317
  state: "warn",
@@ -353,13 +353,13 @@ export class LavalinkManager extends EventEmitter {
353
353
  if (this.initiated)
354
354
  return this;
355
355
  clientData = clientData ?? {};
356
- this.options.client = { ...(this.options?.client || {}), ...clientData };
356
+ this.options.client = { ...this.options?.client, ...clientData };
357
357
  if (!this.options?.client.id)
358
358
  throw new Error('"client.id" is not set. Pass it in Manager#init() or as a option in the constructor.');
359
359
  if (typeof this.options?.client.id !== "string")
360
360
  throw new Error('"client.id" set is not type of "string"');
361
361
  let success = 0;
362
- for (const node of [...this.nodeManager.nodes.values()]) {
362
+ for (const node of this.nodeManager.nodes.values()) {
363
363
  try {
364
364
  await node.connect();
365
365
  success++;
@@ -436,7 +436,7 @@ export class LavalinkManager extends EventEmitter {
436
436
  if (this.options?.advancedOptions?.enableDebugEvents) {
437
437
  this.emit("debug", DebugEvents.NoAudioDebug, {
438
438
  state: "warn",
439
- message: `No Update data found in payload :: ${JSON.stringify(data, null, 2)}`,
439
+ message: `No Update data found in payload :: ${safeStringify(data, 2)}`,
440
440
  functionLayer: "LavalinkManager > sendRawData()",
441
441
  });
442
442
  }
@@ -448,7 +448,7 @@ export class LavalinkManager extends EventEmitter {
448
448
  if (this.options?.advancedOptions?.enableDebugEvents) {
449
449
  this.emit("debug", DebugEvents.NoAudioDebug, {
450
450
  state: "error",
451
- message: `No 'token' nor 'session_id' found in payload :: ${JSON.stringify(data, null, 2)}`,
451
+ message: `No 'token' nor 'session_id' found in payload :: ${safeStringify(data, 2)}`,
452
452
  functionLayer: "LavalinkManager > sendRawData()",
453
453
  });
454
454
  }
@@ -461,7 +461,7 @@ export class LavalinkManager extends EventEmitter {
461
461
  if (this.options?.advancedOptions?.enableDebugEvents) {
462
462
  this.emit("debug", DebugEvents.NoAudioDebug, {
463
463
  state: "warn",
464
- message: `No Lavalink Player found via key: 'guild_id' of update-data :: ${JSON.stringify(update, null, 2)}`,
464
+ message: `No Lavalink Player found via key: 'guild_id' of update-data :: ${safeStringify(update, 2)}`,
465
465
  functionLayer: "LavalinkManager > sendRawData()",
466
466
  });
467
467
  }
@@ -488,11 +488,11 @@ export class LavalinkManager extends EventEmitter {
488
488
  if (!sessionId2Use) {
489
489
  this.emit("debug", DebugEvents.NoAudioDebug, {
490
490
  state: "error",
491
- message: `Can't send updatePlayer for voice token session - Missing sessionId :: ${JSON.stringify({ voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice }, null, 2)}`,
491
+ message: `Can't send updatePlayer for voice token session - Missing sessionId :: ${safeStringify({ voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice }, 2)}`,
492
492
  functionLayer: "LavalinkManager > sendRawData()",
493
493
  });
494
494
  if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
495
- console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
495
+ console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice });
496
496
  }
497
497
  else {
498
498
  await player.node.updatePlayer({
@@ -502,18 +502,18 @@ export class LavalinkManager extends EventEmitter {
502
502
  token: update.token,
503
503
  endpoint: update.endpoint,
504
504
  sessionId: sessionId2Use,
505
- }
506
- }
505
+ },
506
+ },
507
507
  });
508
508
  if (this.options?.advancedOptions?.enableDebugEvents) {
509
509
  this.emit("debug", DebugEvents.NoAudioDebug, {
510
510
  state: "log",
511
- message: `Sent updatePlayer for voice token session :: ${JSON.stringify({ voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice }, null, 2)}`,
511
+ message: `Sent updatePlayer for voice token session :: ${safeStringify({ voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice }, 2)}`,
512
512
  functionLayer: "LavalinkManager > sendRawData()",
513
513
  });
514
514
  }
515
515
  if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
516
- console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, } });
516
+ console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
517
517
  }
518
518
  return;
519
519
  }
@@ -541,12 +541,12 @@ export class LavalinkManager extends EventEmitter {
541
541
  if (this.options?.advancedOptions?.enableDebugEvents) {
542
542
  this.emit("debug", DebugEvents.NoAudioDebug, {
543
543
  state: "warn",
544
- message: `Function to assing sessionId provided, but no found in Payload: ${JSON.stringify({ update, playerVoice: player.voice }, null, 2)}`,
544
+ message: `Function to assing sessionId provided, but no found in Payload: ${safeStringify({ update, playerVoice: player.voice }, 2)}`,
545
545
  functionLayer: "LavalinkManager > sendRawData()",
546
546
  });
547
547
  }
548
548
  if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
549
- console.debug(`Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Function to assing sessionId provided, but no found in Payload: ${JSON.stringify(update, null, 2)}`);
549
+ console.debug(`Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Function to assing sessionId provided, but no found in Payload: ${safeStringify(update, 2)}`);
550
550
  }
551
551
  player.voiceChannelId = update.channel_id;
552
552
  const selfMuteChanged = typeof update.self_mute === "boolean" && player.voiceState.selfMute !== update.self_mute;
@@ -48,6 +48,12 @@ export const DefaultSources = {
48
48
  "vkmusic": "vksearch",
49
49
  "vk music": "vksearch",
50
50
  "vkrec": "vkrec",
51
+ "vk": "vksearch",
52
+ // Qobuz (lavasrc)
53
+ "qbsearch": "qbsearch",
54
+ "qobuz": "qbsearch",
55
+ "qbisrc": "qbisrc",
56
+ "qbrec": "qbrec",
51
57
  // speak PLUGIN
52
58
  "speak": "speak",
53
59
  "tts": "tts",
@@ -70,6 +76,12 @@ export const DefaultSources = {
70
76
  "https": "https",
71
77
  "link": "link",
72
78
  "uri": "uri",
79
+ // tidal
80
+ "tidal": "tdsearch",
81
+ "td": "tdsearch",
82
+ "tidal music": "tdsearch",
83
+ "tdsearch": "tdsearch",
84
+ "tdrec": "tdrec",
73
85
  // jiosaavn
74
86
  "jiosaavn": "jssearch",
75
87
  "js": "jssearch",
@@ -122,8 +134,10 @@ export const SourceLinksRegexes = {
122
134
  SpotifyAlbumRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?album\/(?<identifier>[a-zA-Z0-9-_]+)/,
123
135
  AllSpotifyRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?(?<type>track|album|playlist|artist|episode|show)\/(?<identifier>[a-zA-Z0-9-_]+)/,
124
136
  appleMusic: /https?:\/\/?(?:www\.)?music\.apple\.com\/(\S+)/,
137
+ /** From tidal */
138
+ tidal: /https?:\/\/?(?:www\.)?(?:tidal|listen)\.tidal\.com\/(?<type>track|album|playlist|artist)\/(?<identifier>[a-zA-Z0-9-_]+)/,
125
139
  /** From jiosaavn-plugin */
126
- jiosaavn: /(https?:\/\/)(www\.)?jiosaavn\.com\/(?<type>song|album|featured|artist)\/([a-zA-Z0-9-_\/,]+)/,
140
+ jiosaavn: /(https?:\/\/)(www\.)?jiosaavn\.com\/(?<type>song|album|featured|artist)\/([a-zA-Z0-9-_/,]+)/,
127
141
  /** FROM DUNCTE BOT PLUGIN */
128
142
  tiktok: /https:\/\/www\.tiktok\.com\//,
129
143
  mixcloud: /https:\/\/www\.mixcloud\.com\//,
@@ -1,7 +1,7 @@
1
+ import type { Base64, InvalidLavalinkRestRequest, LavalinkPlayer, LavaSearchQuery, LavaSearchResponse, PlayerUpdateInfo, RoutePlanner, SearchQuery, SearchResult, Session } from "./Types/Utils.js";
1
2
  import type { Player } from "./Player.js";
2
3
  import type { DestroyReasonsType, DisconnectReasonsType } from "./Types/Player.js";
3
4
  import type { Track } from "./Types/Track.js";
4
- import type { Base64, InvalidLavalinkRestRequest, LavalinkPlayer, LavaSearchQuery, LavaSearchResponse, PlayerUpdateInfo, RoutePlanner, SearchQuery, SearchResult, Session } from "./Types/Utils.js";
5
5
  import type { NodeManager } from "./NodeManager.js";
6
6
  import type { BaseNodeStats, LavalinkInfo, LavalinkNodeOptions, LyricsResult, ModifyRequest, NodeStats, SponsorBlockSegment } from "./Types/Node.js";
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  import { isAbsolute } from "path";
2
2
  import WebSocket from "ws";
3
3
  import { DebugEvents, DestroyReasons, validSponsorBlocks } from "./Constants.js";
4
- import { NodeSymbol, queueTrackEnd } from "./Utils.js";
4
+ import { NodeSymbol, queueTrackEnd, safeStringify } from "./Utils.js";
5
5
  /**
6
6
  * Lavalink Node creator class
7
7
  */
@@ -130,7 +130,7 @@ export class LavalinkNode {
130
130
  if (["DELETE", "PUT"].includes(options.method))
131
131
  return;
132
132
  if (response.status === 404)
133
- throw new Error(`Node Request resulted into an error, request-PATH: ${options.path} | headers: ${JSON.stringify(response.headers)}`);
133
+ throw new Error(`Node Request resulted into an error, request-PATH: ${options.path} | headers: ${safeStringify(response.headers)}`);
134
134
  return parseAsText ? await response.text() : await response.json();
135
135
  }
136
136
  /**
@@ -262,7 +262,7 @@ export class LavalinkNode {
262
262
  const res = await this.request(`/sessions/${this.sessionId}/players/${data.guildId}`, r => {
263
263
  r.method = "PATCH";
264
264
  r.headers["Content-Type"] = "application/json";
265
- r.body = JSON.stringify(data.playerOptions);
265
+ r.body = safeStringify(data.playerOptions);
266
266
  if (data.noReplace) {
267
267
  const url = new URL(`${this.restAddress}${r.path}`);
268
268
  url.searchParams.append("noReplace", data.noReplace === true && typeof data.noReplace === "boolean" ? "true" : "false");
@@ -272,7 +272,7 @@ export class LavalinkNode {
272
272
  if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
273
273
  this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateSuccess, {
274
274
  state: "log",
275
- message: `Player get's updated with following payload :: ${JSON.stringify(data.playerOptions, null, 3)}`,
275
+ message: `Player get's updated with following payload :: ${safeStringify(data.playerOptions, 3)}`,
276
276
  functionLayer: "LavalinkNode > node > updatePlayer()",
277
277
  });
278
278
  }
@@ -584,7 +584,7 @@ export class LavalinkNode {
584
584
  return this.request(`/sessions/${this.sessionId}`, r => {
585
585
  r.method = "PATCH";
586
586
  r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
587
- r.body = JSON.stringify(data);
587
+ r.body = safeStringify(data);
588
588
  });
589
589
  }
590
590
  /**
@@ -628,7 +628,7 @@ export class LavalinkNode {
628
628
  // return the decoded + builded tracks
629
629
  return await this.request(`/decodetracks`, r => {
630
630
  r.method = "POST";
631
- r.body = JSON.stringify(encodeds);
631
+ r.body = safeStringify(encodeds);
632
632
  r.headers["Content-Type"] = "application/json";
633
633
  }).then((r) => r.map(track => this.NodeManager.LavalinkManager.utils.buildTrack(track, requester)));
634
634
  }
@@ -804,7 +804,7 @@ export class LavalinkNode {
804
804
  return await this.request(`/routeplanner/free/address`, r => {
805
805
  r.method = "POST";
806
806
  r.headers["Content-Type"] = "application/json";
807
- r.body = JSON.stringify({ address });
807
+ r.body = safeStringify({ address });
808
808
  });
809
809
  },
810
810
  /**
@@ -1214,6 +1214,9 @@ export class LavalinkNode {
1214
1214
  return this.queueEnd(player, track, payload);
1215
1215
  // If a track had an error while starting
1216
1216
  if (["loadFailed", "cleanup"].includes(payload.reason)) {
1217
+ //Dont add tracks if the player is already destroying.
1218
+ if (player.get("internal_destroystatus") === true)
1219
+ return;
1217
1220
  await queueTrackEnd(player);
1218
1221
  // if no track available, end queue
1219
1222
  if (!player.queue.current)
@@ -1267,8 +1270,15 @@ export class LavalinkNode {
1267
1270
  }
1268
1271
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track || this.getTrackOfPayload(payload), payload);
1269
1272
  // If there are no songs in the queue
1270
- if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
1271
- return this.queueEnd(player, track || this.getTrackOfPayload(payload), payload);
1273
+ if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying"))) {
1274
+ try { //Sometimes the trackStuck event triggers from the Lavalink server, but the track continues playing or resumes after. We explicitly end the track in such cases
1275
+ await player.node.updatePlayer({ guildId: player.guildId, playerOptions: { track: { encoded: null } } }); //trackEnd -> queueEnd
1276
+ return;
1277
+ }
1278
+ catch {
1279
+ return this.queueEnd(player, track || this.getTrackOfPayload(payload), payload);
1280
+ }
1281
+ }
1272
1282
  // remove the current track, and enqueue the next one
1273
1283
  await queueTrackEnd(player);
1274
1284
  // if no track available, end queue
@@ -1277,7 +1287,7 @@ export class LavalinkNode {
1277
1287
  }
1278
1288
  // play track if autoSkip is true
1279
1289
  if (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) {
1280
- player.play({ noReplace: true });
1290
+ player.play({ track: player.queue.current, noReplace: false }); // Replace the stuck track with the new track.
1281
1291
  }
1282
1292
  return;
1283
1293
  }
@@ -1370,7 +1380,7 @@ export class LavalinkNode {
1370
1380
  await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (r) => {
1371
1381
  r.method = "PUT";
1372
1382
  r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
1373
- r.body = JSON.stringify(segments.map(v => v.toLowerCase()));
1383
+ r.body = safeStringify(segments.map(v => v.toLowerCase()));
1374
1384
  });
1375
1385
  if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1376
1386
  this.NodeManager.LavalinkManager.emit("debug", DebugEvents.SetSponsorBlock, {
@@ -2,7 +2,7 @@ import { DebugEvents } from "./Constants.js";
2
2
  import { bandCampSearch } from "./CustomSearches/BandCampSearch.js";
3
3
  import { FilterManager } from "./Filters.js";
4
4
  import { Queue, QueueSaver } from "./Queue.js";
5
- import { queueTrackEnd } from "./Utils.js";
5
+ import { queueTrackEnd, safeStringify } from "./Utils.js";
6
6
  export class Player {
7
7
  /** Filter Manager per player */
8
8
  filterManager;
@@ -184,7 +184,11 @@ export class Player {
184
184
  }
185
185
  }
186
186
  if ((typeof options.track?.userData === "object" || typeof options.clientTrack?.userData === "object") && options.clientTrack)
187
- options.clientTrack.userData = { ...(options?.clientTrack.userData || {}), ...(options.track?.userData || {}) };
187
+ options.clientTrack.userData = {
188
+ ...(typeof options?.clientTrack?.requester === "object" ? { requester: this.LavalinkManager.utils.getTransformedRequester(options?.clientTrack?.requester || {}) } : {}),
189
+ ...options?.clientTrack.userData,
190
+ ...options.track?.userData,
191
+ };
188
192
  options.track = {
189
193
  encoded: options.clientTrack?.encoded,
190
194
  requester: options.clientTrack?.requester,
@@ -206,16 +210,11 @@ export class Player {
206
210
  const track = Object.fromEntries(Object.entries({
207
211
  encoded: options.track.encoded,
208
212
  identifier: options.track.identifier,
213
+ userData: {
214
+ ...(typeof options?.track?.requester === "object" ? { requester: this.LavalinkManager.utils.getTransformedRequester(options?.track?.requester || {}) } : {}),
215
+ ...options.track.userData,
216
+ }
209
217
  }).filter(v => typeof v[1] !== "undefined"));
210
- if (typeof options.track.userData === "object")
211
- track.userData = {
212
- ...(options.track.userData || {})
213
- };
214
- if (typeof options?.track?.requester === "object")
215
- track.userData = {
216
- ...(track.userData || {}),
217
- requester: this.LavalinkManager.utils.getTransformedRequester(options?.track?.requester || {})
218
- };
219
218
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
220
219
  this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayWithTrackReplace, {
221
220
  state: "log",
@@ -251,7 +250,11 @@ export class Player {
251
250
  // resolve the unresolved track
252
251
  await this.queue.current.resolve(this);
253
252
  if (typeof options.track?.userData === "object" && this.queue.current)
254
- this.queue.current.userData = { ...(this.queue.current?.userData || {}), ...(options.track?.userData || {}) };
253
+ this.queue.current.userData = {
254
+ ...(typeof this.queue.current?.requester === "object" ? { requester: this.LavalinkManager.utils.getTransformedRequester(this.queue.current?.requester || {}) } : {}),
255
+ ...this.queue.current?.userData,
256
+ ...options.track?.userData
257
+ };
255
258
  }
256
259
  catch (error) {
257
260
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
@@ -267,6 +270,8 @@ export class Player {
267
270
  delete options.clientTrack;
268
271
  if (options && "track" in options)
269
272
  delete options.track;
273
+ // get rid of the current song without shifting the queue, so that the shifting can happen inside the next .play() call when "autoSkipOnResolveError" is true
274
+ await queueTrackEnd(this, true);
270
275
  // try to play the next track if possible
271
276
  if (this.LavalinkManager.options?.autoSkipOnResolveError === true && this.queue.tracks[0])
272
277
  return this.play(options);
@@ -287,7 +292,11 @@ export class Player {
287
292
  track: {
288
293
  encoded: this.queue.current?.encoded || null,
289
294
  // identifier: options.identifier,
290
- userData: options?.track?.userData || {},
295
+ userData: {
296
+ ...(typeof this.queue.current?.requester === "object" ? { requester: this.LavalinkManager.utils.getTransformedRequester(this.queue.current?.requester || {}) } : {}),
297
+ ...options?.track?.userData,
298
+ ...this.queue.current?.userData,
299
+ },
291
300
  },
292
301
  volume: this.lavalinkVolume,
293
302
  position: options?.position ?? 0,
@@ -402,6 +411,8 @@ export class Player {
402
411
  const now = performance.now();
403
412
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: true } });
404
413
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
414
+ // emit the event
415
+ this.LavalinkManager.emit("playerPaused", this, this.queue.current);
405
416
  return this;
406
417
  }
407
418
  /**
@@ -414,6 +425,8 @@ export class Player {
414
425
  const now = performance.now();
415
426
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: false } });
416
427
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
428
+ // emit the event
429
+ this.LavalinkManager.emit("playerResumed", this, this.queue.current);
417
430
  return this;
418
431
  }
419
432
  /**
@@ -679,6 +692,7 @@ export class Player {
679
692
  }
680
693
  const data = this.toJSON();
681
694
  const currentTrack = this.queue.current;
695
+ const segments = await this.getSponsorBlock().catch(() => []);
682
696
  const voiceData = this.voice;
683
697
  if (!voiceData.endpoint ||
684
698
  !voiceData.sessionId ||
@@ -695,7 +709,7 @@ export class Player {
695
709
  await this.node.request(endpoint, r => {
696
710
  r.method = "PATCH";
697
711
  r.headers["Content-Type"] = "application/json";
698
- r.body = JSON.stringify({
712
+ r.body = safeStringify({
699
713
  voice: {
700
714
  token: voiceData.token,
701
715
  endpoint: voiceData.endpoint,
@@ -703,6 +717,33 @@ export class Player {
703
717
  }
704
718
  });
705
719
  });
720
+ const hasSponsorBlock = this.node.info?.plugins?.find(v => v.name === "sponsorblock-plugin");
721
+ if (hasSponsorBlock) {
722
+ if (segments.length) {
723
+ await this.setSponsorBlock(segments).catch(error => {
724
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
725
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
726
+ state: "error",
727
+ error: error,
728
+ message: `Player > changeNode() Unable to set SponsorBlock Segments`,
729
+ functionLayer: "Player > changeNode()",
730
+ });
731
+ }
732
+ });
733
+ }
734
+ else {
735
+ await this.setSponsorBlock().catch(error => {
736
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
737
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
738
+ state: "error",
739
+ error: error,
740
+ message: `Player > changeNode() Unable to set SponsorBlock Segments`,
741
+ functionLayer: "Player > changeNode()",
742
+ });
743
+ }
744
+ });
745
+ }
746
+ }
706
747
  if (currentTrack) { // If there is a current track, send it to the new node.
707
748
  await this.node.updatePlayer({
708
749
  guildId: this.guildId,
@@ -716,6 +757,8 @@ export class Player {
716
757
  }
717
758
  });
718
759
  }
760
+ this.paused = data.paused;
761
+ this.playing = data.playing;
719
762
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
720
763
  return this.node.id;
721
764
  }
@@ -172,6 +172,8 @@ export interface LavalinkManagerEvents {
172
172
  * @event Manager#LyricsNotFound
173
173
  */
174
174
  "LyricsNotFound": (player: Player, track: Track | UnresolvedTrack | null, payload: LyricsNotFoundEvent) => void;
175
+ "playerResumed": (player: Player, track: Track | UnresolvedTrack | null) => void;
176
+ "playerPaused": (player: Player, track: Track | UnresolvedTrack | null) => void;
175
177
  }
176
178
  /**
177
179
  * The Bot client Options needed for the manager
@@ -4,7 +4,7 @@ import type { Base64 } from "./Utils.js";
4
4
  /** Sourcenames provided by lavalink server */
5
5
  export type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
6
6
  /** Source Names provided by lava src plugin */
7
- export type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts";
7
+ export type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts" | "vkmusic" | "tidal" | "qobuz";
8
8
  /** Source Names provided by jiosaavan plugin */
9
9
  export type LavalinkPlugin_JioSaavn_SourceNames = "jiosaavn";
10
10
  /** The SourceNames provided by lavalink */
@@ -11,7 +11,7 @@ export type Opaque<T, K> = T & {
11
11
  export type IntegerNumber = Opaque<number, 'Int'>;
12
12
  /** Opqaue tyep for floatnumber */
13
13
  export type FloatNumber = Opaque<number, 'Float'>;
14
- export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "dzrec" | "ymsearch" | "ymrec" | "vksearch" | "vkrec";
14
+ export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "dzrec" | "ymsearch" | "ymrec" | "vksearch" | "vkrec" | "tdsearch" | "tdrec" | "qbsearch" | "qbisrc" | "qbrec";
15
15
  export type LavaSrcSearchPlatform = LavaSrcSearchPlatformBase | "ftts";
16
16
  export type JioSaavnSearchPlatform = "jssearch" | "jsrec";
17
17
  export type DuncteSearchPlatform = "speak" | "phsearch" | "pornhub" | "porn" | "tts";
@@ -20,9 +20,9 @@ export type LavalinkClientSearchPlatformResolve = "bandcamp" | "bc";
20
20
  export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "bcsearch" | LavaSrcSearchPlatform | DuncteSearchPlatform | JioSaavnSearchPlatform | LavalinkClientSearchPlatform;
21
21
  export type ClientCustomSearchPlatformUtils = "local" | "http" | "https" | "link" | "uri";
22
22
  export type ClientSearchPlatform = ClientCustomSearchPlatformUtils | // for file/link requests
23
- "youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "musicyoutube" | "music youtube" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "musicapple" | "music apple" | "sp" | "spsuggestion" | "spotify" | "spotify.com" | "spotifycom" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic" | "vk music" | "vkmusic" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn";
23
+ "youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "musicyoutube" | "music youtube" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "musicapple" | "music apple" | "sp" | "spsuggestion" | "spotify" | "spotify.com" | "spotifycom" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic" | "vk" | "vk music" | "vkmusic" | "tidal" | "tidal music" | "qobuz" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn" | "td" | "tidal" | "tdrec";
24
24
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
25
- export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "jiosaavn" | "appleMusic" | "TwitchTv" | "vimeo";
25
+ export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "jiosaavn" | "appleMusic" | "tidal" | "TwitchTv" | "vimeo";
26
26
  export interface PlaylistInfo {
27
27
  /** The playlist name */
28
28
  name: string;
@@ -92,13 +92,13 @@ export interface TrackEndEvent extends PlayerEvent {
92
92
  export interface TrackExceptionEvent extends PlayerEvent {
93
93
  type: "TrackExceptionEvent";
94
94
  exception?: Exception;
95
- tracK: LavalinkTrack;
95
+ track: LavalinkTrack;
96
96
  error: string;
97
97
  }
98
98
  export interface TrackStuckEvent extends PlayerEvent {
99
99
  type: "TrackStuckEvent";
100
100
  thresholdMs: number;
101
- tracK: LavalinkTrack;
101
+ track: LavalinkTrack;
102
102
  }
103
103
  export interface WebSocketClosedEvent extends PlayerEvent {
104
104
  type: "WebSocketClosedEvent";
@@ -112,4 +112,5 @@ export declare class MiniMap<K, V> extends Map<K, V> {
112
112
  map<T>(fn: (value: V, key: K, miniMap: this) => T): T[];
113
113
  map<This, T>(fn: (this: This, value: V, key: K, miniMap: this) => T, thisArg: This): T[];
114
114
  }
115
- export declare function queueTrackEnd(player: Player): Promise<Track>;
115
+ export declare function queueTrackEnd(player: Player, dontShiftQueue?: boolean): Promise<Track>;
116
+ export declare function safeStringify(obj: any, padding?: number): string;
@@ -302,6 +302,9 @@ export class ManagerUtils {
302
302
  if (SourceLinksRegexes.jiosaavn.test(queryString) && !node.info?.sourceManagers?.includes("jiosaavn")) {
303
303
  throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'jiosaavn' (via jiosaavn-plugin) enabled");
304
304
  }
305
+ if (SourceLinksRegexes.tidal.test(queryString) && !node.info?.sourceManagers?.includes("tidal")) {
306
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'tidal' enabled");
307
+ }
305
308
  return;
306
309
  }
307
310
  transformQuery(query) {
@@ -366,6 +369,12 @@ export class ManagerUtils {
366
369
  if (source === "speak" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkPlugins.DuncteBot_Plugin.toLowerCase()))) {
367
370
  throw new Error("Lavalink Node has not 'speak' enabled, which is required to have 'speak' work");
368
371
  }
372
+ if (source === "tdsearch" && !node.info?.sourceManagers?.includes("tidal")) {
373
+ throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdsearch' work");
374
+ }
375
+ if (source === "tdrec" && !node.info?.sourceManagers?.includes("tidal")) {
376
+ throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdrec' work");
377
+ }
369
378
  if (source === "tts" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkPlugins.GoogleCloudTTS.toLowerCase()))) {
370
379
  throw new Error("Lavalink Node has not 'tts' enabled, which is required to have 'tts' work");
371
380
  }
@@ -381,6 +390,21 @@ export class ManagerUtils {
381
390
  if (source === "ytsearch" && !node.info?.sourceManagers?.includes("youtube")) {
382
391
  throw new Error("Lavalink Node has not 'youtube' enabled, which is required to have 'ytsearch' work");
383
392
  }
393
+ if (source === "vksearch" && !node.info?.sourceManagers?.includes("vkmusic")) {
394
+ throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vksearch' work");
395
+ }
396
+ if (source === "vkrec" && !node.info?.sourceManagers?.includes("vkmusic")) {
397
+ throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vkrec' work");
398
+ }
399
+ if (source === "qbsearch" && !node.info?.sourceManagers?.includes("qobuz")) {
400
+ throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbsearch' work");
401
+ }
402
+ if (source === "qbisrc" && !node.info?.sourceManagers?.includes("qobuz")) {
403
+ throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbisrc' work");
404
+ }
405
+ if (source === "qbrec" && !node.info?.sourceManagers?.includes("qobuz")) {
406
+ throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbrec' work");
407
+ }
384
408
  return;
385
409
  }
386
410
  }
@@ -411,7 +435,7 @@ export class MiniMap extends Map {
411
435
  });
412
436
  }
413
437
  }
414
- export async function queueTrackEnd(player) {
438
+ export async function queueTrackEnd(player, dontShiftQueue = false) {
415
439
  if (player.queue.current && !player.queue.current?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
416
440
  player.queue.previous.unshift(player.queue.current);
417
441
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
@@ -422,9 +446,9 @@ export async function queueTrackEnd(player) {
422
446
  if (player.repeatMode === "queue" && player.queue.current)
423
447
  player.queue.tracks.push(player.queue.current);
424
448
  // change the current Track to the next upcoming one
425
- const nextSong = player.queue.tracks.shift();
449
+ const nextSong = dontShiftQueue ? null : player.queue.tracks.shift();
426
450
  try {
427
- if (player.LavalinkManager.utils.isUnresolvedTrack(nextSong))
451
+ if (nextSong && player.LavalinkManager.utils.isUnresolvedTrack(nextSong))
428
452
  await nextSong.resolve(player);
429
453
  player.queue.current = nextSong || null;
430
454
  // save it in the DB
@@ -441,7 +465,7 @@ export async function queueTrackEnd(player) {
441
465
  }
442
466
  player.LavalinkManager.emit("trackError", player, player.queue.current, error);
443
467
  // try to play the next track if possible
444
- if (player.LavalinkManager.options?.autoSkipOnResolveError === true && player.queue.tracks[0])
468
+ if (!dontShiftQueue && player.LavalinkManager.options?.autoSkipOnResolveError === true && player.queue.tracks[0])
445
469
  return queueTrackEnd(player);
446
470
  }
447
471
  // return the new current Track
@@ -516,3 +540,20 @@ async function getClosestTrack(data, player) {
516
540
  return applyUnresolvedData(trackToUse || res.tracks[0], data, player.LavalinkManager.utils);
517
541
  });
518
542
  }
543
+ export function safeStringify(obj, padding = 0) {
544
+ const seen = new WeakSet();
545
+ return JSON.stringify(obj, (key, value) => {
546
+ if (typeof value === "function")
547
+ return undefined; // Funktion skippen
548
+ if (typeof value === "symbol")
549
+ return undefined; // Symbol skippen
550
+ if (typeof value === "bigint")
551
+ return value.toString(); // BigInt to String
552
+ if (typeof value === "object" && value !== null) {
553
+ if (seen.has(value))
554
+ return "[Circular]";
555
+ seen.add(value);
556
+ }
557
+ return value;
558
+ }, padding);
559
+ }